@czi-sds/data-viz 2.4.3 → 2.5.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.
@@ -1,28 +1,30 @@
1
1
  "use client";
2
+ const require_chunk = require("./chunk.cjs.js");
2
3
  const require_objectSpread2 = require("./objectSpread2.cjs.js");
4
+ const require_renderStructureImage = require("./renderStructureImage.cjs.js");
3
5
  const require_colorScales = require("./colorScales.cjs.js");
4
6
  let react = require("react");
5
7
  let _emotion_styled = require("@emotion/styled");
6
- _emotion_styled = require_objectSpread2.__toESM(_emotion_styled);
8
+ _emotion_styled = require_chunk.__toESM(_emotion_styled);
7
9
  let react_jsx_runtime = require("react/jsx-runtime");
10
+ let molstar_lib_mol_math_linear_algebra = require("molstar/lib/mol-math/linear-algebra");
11
+ let molstar_lib_mol_model_loci = require("molstar/lib/mol-model/loci");
12
+ let molstar_lib_mol_data_int = require("molstar/lib/mol-data/int");
13
+ let molstar_lib_mol_model_structure = require("molstar/lib/mol-model/structure");
8
14
  let _czi_sds_components = require("@czi-sds/components");
9
15
  let _mui_material_styles = require("@mui/material/styles");
10
16
  let _phosphor_icons_react = require("@phosphor-icons/react");
11
- let molstar_lib_mol_model_structure = require("molstar/lib/mol-model/structure");
12
17
  let molstar_lib_mol_plugin_state_helpers_structure_transparency = require("molstar/lib/mol-plugin-state/helpers/structure-transparency");
13
18
  let molstar_lib_mol_plugin_ui = require("molstar/lib/mol-plugin-ui");
14
19
  let molstar_lib_mol_plugin_ui_react18 = require("molstar/lib/mol-plugin-ui/react18");
15
20
  let molstar_lib_mol_plugin_ui_spec = require("molstar/lib/mol-plugin-ui/spec");
16
21
  let molstar_lib_mol_plugin_behavior = require("molstar/lib/mol-plugin/behavior");
17
- let molstar_lib_mol_plugin_behavior_static_state = require("molstar/lib/mol-plugin/behavior/static/state");
18
22
  let molstar_lib_mol_plugin_config = require("molstar/lib/mol-plugin/config");
19
23
  let molstar_lib_mol_repr_representation = require("molstar/lib/mol-repr/representation");
20
24
  let rxjs = require("rxjs");
21
- let molstar_lib_mol_model_loci = require("molstar/lib/mol-model/loci");
22
25
  let molstar_lib_mol_plugin_ui_base = require("molstar/lib/mol-plugin-ui/base");
23
26
  let molstar_lib_mol_util_input_input_observer = require("molstar/lib/mol-util/input/input-observer");
24
27
  let rxjs_operators = require("rxjs/operators");
25
- let molstar_lib_mol_data_int = require("molstar/lib/mol-data/int");
26
28
  let _mui_material = require("@mui/material");
27
29
  let molstar_lib_mol_plugin_state_objects = require("molstar/lib/mol-plugin-state/objects");
28
30
  let molstar_lib_mol_plugin_ui_sequence = require("molstar/lib/mol-plugin-ui/sequence");
@@ -31,13 +33,7 @@ let molstar_lib_mol_util = require("molstar/lib/mol-util");
31
33
  let molstar_lib_mol_util_param_definition = require("molstar/lib/mol-util/param-definition");
32
34
  let molstar_lib_mol_plugin_ui_toast = require("molstar/lib/mol-plugin-ui/toast");
33
35
  let molstar_lib_mol_plugin_ui_viewport = require("molstar/lib/mol-plugin-ui/viewport");
34
- let molstar_lib_mol_util_color = require("molstar/lib/mol-util/color");
35
36
  let molstar_lib_mol_plugin_commands = require("molstar/lib/mol-plugin/commands");
36
- let molstar_lib_mol_math_linear_algebra = require("molstar/lib/mol-math/linear-algebra");
37
- let molstar_lib_mol_model_structure_model_types = require("molstar/lib/mol-model/structure/model/types");
38
- let molstar_lib_mol_theme_label = require("molstar/lib/mol-theme/label");
39
- let molstar_lib_mol_script_language_builder = require("molstar/lib/mol-script/language/builder");
40
- let molstar_lib_mol_model_props_common_custom_element_property = require("molstar/lib/mol-model-props/common/custom-element-property");
41
37
  let molstar_lib_mol_plugin_behavior_dynamic_selection_structure_focus_representation = require("molstar/lib/mol-plugin/behavior/dynamic/selection/structure-focus-representation");
42
38
  //#region src/core/ProteinStructureViewer/components/ChainLegend/style.ts
43
39
  /** Opacity applied to a hidden chain's row, so it reads as off but reachable. */
@@ -296,17 +292,35 @@ const MIN_WIDTH = 200;
296
292
  */
297
293
  const VIEWER_CONTAINER_NAME = "sds-structure-viewer";
298
294
  /**
299
- * Height reserved for the sequence panel along the bottom, and the viewer
300
- * width above which it grows. Measured against the container above rather than
301
- * the page, so a viewer embedded in a wider page sizes to the room it has.
295
+ * Height reserved for the sequence panel along the bottom when the consumer
296
+ * sets none, and the viewer width above which it grows. Measured against the
297
+ * container above rather than the page, so a viewer embedded in a wider page
298
+ * sizes to the room it has.
302
299
  *
303
- * The legend sits directly on top of the panel and offsets itself by these
304
- * same three values, so it reads them from here rather than restating them.
305
- * Copies that drifted would leave the legend floating off the panel.
300
+ * The legend sits directly on top of the panel and offsets itself by the same
301
+ * heights at the same breakpoint, so it reads them from here rather than
302
+ * restating them. Copies that drifted would leave the legend floating off the
303
+ * panel.
306
304
  */
307
305
  const SEQUENCE_HEIGHT = "max(104px, 30%)";
308
306
  const SEQUENCE_HEIGHT_WIDE = "max(134px, 32%)";
309
307
  /**
308
+ * The sequence panel's height below `WIDE_VIEWER` and at or above it. A height
309
+ * the consumer sets holds at every width, a number is read as pixels, and an
310
+ * empty string leaves the defaults in place, as it would in a React style.
311
+ */
312
+ function sequenceHeights(height) {
313
+ if (height === void 0 || height === "") return {
314
+ base: SEQUENCE_HEIGHT,
315
+ wide: SEQUENCE_HEIGHT_WIDE
316
+ };
317
+ const css = typeof height === "number" ? `${height}px` : height;
318
+ return {
319
+ base: css,
320
+ wide: css
321
+ };
322
+ }
323
+ /**
310
324
  * The element Mol* mounts into.
311
325
  *
312
326
  * This has to be its own node rather than the root: Mol* takes ownership of its
@@ -383,17 +397,20 @@ const ViewerRoot = (0, _emotion_styled.default)("div")`
383
397
  /*
384
398
  * Mol* puts the sequence panel in its "top" region; move it to the bottom and
385
399
  * shrink the main viewport region to match, so the structure sits above the
386
- * sequence rather than behind it. The panel grows on wider viewers, and the
387
- * viewport region's offset follows it.
400
+ * sequence rather than behind it. The panel grows on wider viewers unless the
401
+ * consumer has set its height, and the viewport region's offset follows it.
388
402
  */
389
403
  ${(props) => {
390
- const { showSequenceViewer } = props;
404
+ const { sequenceViewerHeight, showSequenceViewer } = props;
405
+ const height = sequenceHeights(sequenceViewerHeight);
406
+ const mainBottom = showSequenceViewer ? height.base : "0";
407
+ const mainBottomWide = showSequenceViewer ? height.wide : "0";
391
408
  return `
392
409
  .msp-plugin .msp-layout-region.msp-layout-top {
393
410
  display: ${showSequenceViewer ? "block" : "none"} !important;
394
411
  position: absolute !important;
395
412
  inset: auto 0 0 0 !important;
396
- height: ${SEQUENCE_HEIGHT} !important;
413
+ height: ${height.base} !important;
397
414
  }
398
415
 
399
416
  .msp-plugin .msp-layout-region.msp-layout-main {
@@ -401,16 +418,16 @@ const ViewerRoot = (0, _emotion_styled.default)("div")`
401
418
  top: 0 !important;
402
419
  left: 0 !important;
403
420
  right: 0 !important;
404
- bottom: ${showSequenceViewer ? SEQUENCE_HEIGHT : "0"} !important;
421
+ bottom: ${mainBottom} !important;
405
422
  }
406
423
 
407
424
  @container ${VIEWER_CONTAINER_NAME} (min-width: 880px) {
408
425
  .msp-plugin .msp-layout-region.msp-layout-top {
409
- height: ${SEQUENCE_HEIGHT_WIDE} !important;
426
+ height: ${height.wide} !important;
410
427
  }
411
428
 
412
429
  .msp-plugin .msp-layout-region.msp-layout-main {
413
- bottom: ${showSequenceViewer ? SEQUENCE_HEIGHT_WIDE : "0"} !important;
430
+ bottom: ${mainBottomWide} !important;
414
431
  }
415
432
  }
416
433
  `;
@@ -452,12 +469,13 @@ const LegendOverlay = (0, _emotion_styled.default)("div")`
452
469
 
453
470
  ${(props) => {
454
471
  const spaces = (0, _czi_sds_components.getSpaces)(props);
455
- const { showSequenceViewer } = props;
472
+ const { sequenceViewerHeight, showSequenceViewer } = props;
473
+ const height = sequenceHeights(sequenceViewerHeight);
456
474
  return `
457
475
  right: ${spaces === null || spaces === void 0 ? void 0 : spaces.m}px;
458
476
  gap: ${spaces === null || spaces === void 0 ? void 0 : spaces.xs}px;
459
477
  padding-bottom: ${spaces === null || spaces === void 0 ? void 0 : spaces.m}px;
460
- bottom: ${showSequenceViewer ? SEQUENCE_HEIGHT : "0"};
478
+ bottom: ${showSequenceViewer ? height.base : "0"};
461
479
 
462
480
  /*
463
481
  * Track the sequence panel as it grows on wider viewers. The panel is
@@ -465,7 +483,7 @@ const LegendOverlay = (0, _emotion_styled.default)("div")`
465
483
  * would part the legend from it on a viewer narrower than its page.
466
484
  */
467
485
  @container ${VIEWER_CONTAINER_NAME} (min-width: 880px) {
468
- bottom: ${showSequenceViewer ? SEQUENCE_HEIGHT_WIDE : "0"};
486
+ bottom: ${showSequenceViewer ? height.wide : "0"};
469
487
  }
470
488
 
471
489
  /* Spacer balancing the axes widget pinned in the opposite corner. */
@@ -641,7 +659,7 @@ function readoutSlot(label, value, residueCount) {
641
659
  * residue takes priority so the user can inspect others without losing their
642
660
  * selection.
643
661
  */
644
- function StructureLegend({ chainBandColors, chainColors, chains = [], hiddenChains, hoveredResidue = null, onChainHover, onChainSelect, onChainToggle, scale, scaleLabel, scaleMax = null, scaleMin = 0, scaleTooltip, scaleTooltipProps, selectedChains, selectedResidue = null, showSequenceViewer, stats, valueLabel }) {
662
+ function StructureLegend({ chainBandColors, chainColors, chains = [], hiddenChains, hoveredResidue = null, onChainHover, onChainSelect, onChainToggle, scale, scaleLabel, scaleMax = null, scaleMin = 0, scaleTooltip, scaleTooltipProps, selectedChains, selectedResidue = null, sequenceViewerHeight, showSequenceViewer, stats, valueLabel }) {
645
663
  var _activeResidue$residu, _stats$, _stats$2, _stats$3;
646
664
  const activeResidue = hoveredResidue !== null && hoveredResidue !== void 0 ? hoveredResidue : selectedResidue;
647
665
  const showResidue = activeResidue !== null;
@@ -656,6 +674,7 @@ function StructureLegend({ chainBandColors, chainColors, chains = [], hiddenChai
656
674
  showResidue ? readoutSlot("pLDDT", activeResidue.plddt, residueCount) : (_stats$3 = stats[2]) !== null && _stats$3 !== void 0 ? _stats$3 : null
657
675
  ];
658
676
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(LegendOverlay, {
677
+ sequenceViewerHeight,
659
678
  showSequenceViewer,
660
679
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatsGrid, { children: Array.from({ length: SLOT_COUNT }, (_, i) => {
661
680
  var _slots$i;
@@ -690,30 +709,71 @@ function StructureLegend({ chainBandColors, chainColors, chains = [], hiddenChai
690
709
  });
691
710
  }
692
711
  //#endregion
693
- //#region \0@oxc-project+runtime@0.134.0/helpers/esm/asyncToGenerator.js
694
- function asyncGeneratorStep(n, t, e, r, o, a, c) {
695
- try {
696
- var i = n[a](c), u = i.value;
697
- } catch (n) {
698
- e(n);
699
- return;
700
- }
701
- i.done ? t(u) : Promise.resolve(u).then(r, o);
702
- }
703
- function _asyncToGenerator(n) {
704
- return function() {
705
- var t = this, e = arguments;
706
- return new Promise(function(r, o) {
707
- var a = n.apply(t, e);
708
- function _next(n) {
709
- asyncGeneratorStep(a, r, o, _next, _throw, "next", n);
710
- }
711
- function _throw(n) {
712
- asyncGeneratorStep(a, r, o, _next, _throw, "throw", n);
713
- }
714
- _next(void 0);
712
+ //#region src/core/ProteinStructureViewer/hooks/useCamera.ts
713
+ /**
714
+ * How long the camera has to stay put before it counts as having come to
715
+ * rest, in ms. Mol* draws on demand, so a camera that has stopped stops the
716
+ * draws, and a trackball still coasting after a drag keeps pushing this back.
717
+ */
718
+ const CAMERA_SETTLE_MS = 150;
719
+ /**
720
+ * Keeps the camera in step with the camera props after a structure is up:
721
+ * the projection, the orientation, and reporting where the camera comes to
722
+ * rest. Where the camera starts on a structure is decided by the load, which
723
+ * knows when there is something drawn to place it against.
724
+ */
725
+ function useCamera({ framedOrientationRef, highlights, isReady, onCameraChange, orientation, pluginRef, projection, sceneRef }) {
726
+ const highlightsRef = (0, react.useRef)(highlights);
727
+ highlightsRef.current = highlights;
728
+ const onCameraChangeRef = (0, react.useRef)(onCameraChange);
729
+ onCameraChangeRef.current = onCameraChange;
730
+ (0, react.useEffect)(() => {
731
+ var _pluginRef$current;
732
+ const canvas3d = (_pluginRef$current = pluginRef.current) === null || _pluginRef$current === void 0 ? void 0 : _pluginRef$current.canvas3d;
733
+ if (!canvas3d || !isReady || !projection) return;
734
+ if (canvas3d.props.camera.mode === projection) return;
735
+ canvas3d.setProps({ camera: { mode: projection } });
736
+ }, [
737
+ isReady,
738
+ pluginRef,
739
+ projection
740
+ ]);
741
+ (0, react.useEffect)(() => {
742
+ const scene = sceneRef.current;
743
+ if (!scene || !isReady) return;
744
+ if (orientation === framedOrientationRef.current) return;
745
+ framedOrientationRef.current = orientation;
746
+ if (!orientation) return;
747
+ scene.orient(orientation, highlightsRef.current, 250);
748
+ }, [
749
+ framedOrientationRef,
750
+ isReady,
751
+ orientation,
752
+ sceneRef
753
+ ]);
754
+ (0, react.useEffect)(() => {
755
+ var _pluginRef$current2;
756
+ const canvas3d = (_pluginRef$current2 = pluginRef.current) === null || _pluginRef$current2 === void 0 ? void 0 : _pluginRef$current2.canvas3d;
757
+ if (!canvas3d || !isReady) return;
758
+ let timer;
759
+ let reported = null;
760
+ const subscription = canvas3d.didDraw.subscribe(() => {
761
+ if (!onCameraChangeRef.current) return;
762
+ clearTimeout(timer);
763
+ timer = setTimeout(() => {
764
+ var _onCameraChangeRef$cu;
765
+ if (canvas3d.camera.transition.inTransition) return;
766
+ const state = require_renderStructureImage.readCameraState(canvas3d);
767
+ if (reported && require_renderStructureImage.camerasEqual(reported, state)) return;
768
+ reported = state;
769
+ (_onCameraChangeRef$cu = onCameraChangeRef.current) === null || _onCameraChangeRef$cu === void 0 || _onCameraChangeRef$cu.call(onCameraChangeRef, state);
770
+ }, CAMERA_SETTLE_MS);
715
771
  });
716
- };
772
+ return () => {
773
+ subscription.unsubscribe();
774
+ clearTimeout(timer);
775
+ };
776
+ }, [isReady, pluginRef]);
717
777
  }
718
778
  //#endregion
719
779
  //#region src/core/ProteinStructureViewer/hooks/useChainHighlight.ts
@@ -733,13 +793,20 @@ const CHAIN_DIM_TRANSPARENCY = .65;
733
793
  * part name and stopping at the comma is what keeps `A` from also matching the
734
794
  * chain named `AA`.
735
795
  */
736
- const COMPONENT_CHAIN_ID = /structure-component-(?:polymer|ligand)-([^,]+)/;
796
+ const COMPONENT_CHAIN_ID = /structure-component-(?:polymer|ligand|highlight)-([^,]+)/;
797
+ /** The one component spanning chains: the surface over the visible ones. */
798
+ const SURFACE_COMPONENT = /structure-component-surface(?:,|$)/;
737
799
  /** The chain a component was built for, or undefined if it is not a chain's. */
738
800
  function componentChainId(component) {
739
801
  var _component$key, _COMPONENT_CHAIN_ID$e;
740
802
  const key = (_component$key = component.key) !== null && _component$key !== void 0 ? _component$key : component.cell.transform.ref;
741
803
  return (_COMPONENT_CHAIN_ID$e = COMPONENT_CHAIN_ID.exec(key)) === null || _COMPONENT_CHAIN_ID$e === void 0 ? void 0 : _COMPONENT_CHAIN_ID$e[1];
742
804
  }
805
+ /** True for the surface, which draws every visible chain in one component. */
806
+ function isSurfaceComponent(component) {
807
+ var _component$key2;
808
+ return SURFACE_COMPONENT.test((_component$key2 = component.key) !== null && _component$key2 !== void 0 ? _component$key2 : component.cell.transform.ref);
809
+ }
743
810
  /** Every chain component of every loaded structure, polymers and ligands. */
744
811
  function structureComponents(plugin) {
745
812
  return plugin.managers.structure.hierarchy.current.structures.flatMap((entry) => entry.components);
@@ -760,11 +827,11 @@ function chainsKey(chains) {
760
827
  * arriving before the first load looks like. It is applied again after the load
761
828
  * rather than being taken as done.
762
829
  */
763
- function dimChainsOutside(_x2, _x3) {
830
+ function dimChainsOutside(_x3, _x4) {
764
831
  return _dimChainsOutside.apply(this, arguments);
765
832
  }
766
833
  function _dimChainsOutside() {
767
- _dimChainsOutside = _asyncToGenerator(function* (plugin, lit) {
834
+ _dimChainsOutside = require_renderStructureImage._asyncToGenerator(function* (plugin, lit) {
768
835
  const components = structureComponents(plugin);
769
836
  if (components.length === 0) return false;
770
837
  yield (0, molstar_lib_mol_plugin_state_helpers_structure_transparency.clearStructureTransparency)(plugin, components);
@@ -773,15 +840,24 @@ function _dimChainsOutside() {
773
840
  const chainId = componentChainId(component);
774
841
  return chainId !== void 0 && !lit.has(chainId);
775
842
  });
776
- if (dimmed.length === 0) return true;
777
- yield (0, molstar_lib_mol_plugin_state_helpers_structure_transparency.setStructureTransparency)(plugin, dimmed, CHAIN_DIM_TRANSPARENCY, function() {
778
- var _ref = _asyncToGenerator(function* (structure) {
843
+ if (dimmed.length > 0) yield (0, molstar_lib_mol_plugin_state_helpers_structure_transparency.setStructureTransparency)(plugin, dimmed, CHAIN_DIM_TRANSPARENCY, function() {
844
+ var _ref = require_renderStructureImage._asyncToGenerator(function* (structure) {
779
845
  return molstar_lib_mol_model_structure.Structure.toStructureElementLoci(structure);
780
846
  });
781
847
  return function(_x) {
782
848
  return _ref.apply(this, arguments);
783
849
  };
784
850
  }());
851
+ const surfaces = components.filter(isSurfaceComponent);
852
+ if (surfaces.length > 0) yield (0, molstar_lib_mol_plugin_state_helpers_structure_transparency.setStructureTransparency)(plugin, surfaces, CHAIN_DIM_TRANSPARENCY, function() {
853
+ var _ref2 = require_renderStructureImage._asyncToGenerator(function* (structure) {
854
+ var _lociOutsideChains;
855
+ return (_lociOutsideChains = require_renderStructureImage.lociOutsideChains(structure, lit)) !== null && _lociOutsideChains !== void 0 ? _lociOutsideChains : molstar_lib_mol_model_loci.EmptyLoci;
856
+ });
857
+ return function(_x2) {
858
+ return _ref2.apply(this, arguments);
859
+ };
860
+ }());
785
861
  return true;
786
862
  });
787
863
  return _dimChainsOutside.apply(this, arguments);
@@ -806,7 +882,7 @@ function _dimChainsOutside() {
806
882
  * came to rest on, instead of working through a queue of hovers that have
807
883
  * already been left.
808
884
  */
809
- function useChainHighlight({ disabled, hiddenChains, loadCount, pluginRef, selectedChains }) {
885
+ function useChainHighlight({ disabled, hiddenChains, loadCount, pluginRef, sceneVersion, selectedChains }) {
810
886
  const hoveredRef = (0, react.useRef)(null);
811
887
  /**
812
888
  * The chains left lit on the canvas as it stands. A freshly loaded structure
@@ -834,7 +910,7 @@ function useChainHighlight({ disabled, hiddenChains, loadCount, pluginRef, selec
834
910
  if (!hoverOff && hovered !== null && !hidden.has(hovered)) return new Set([hovered]);
835
911
  return new Set([...selected].filter((chainId) => !hidden.has(chainId)));
836
912
  }, []);
837
- const settle = (0, react.useCallback)(_asyncToGenerator(function* () {
913
+ const settle = (0, react.useCallback)(require_renderStructureImage._asyncToGenerator(function* () {
838
914
  if (committingRef.current) return;
839
915
  committingRef.current = true;
840
916
  try {
@@ -866,51 +942,14 @@ function useChainHighlight({ disabled, hiddenChains, loadCount, pluginRef, selec
866
942
  (0, react.useEffect)(() => {
867
943
  shownRef.current = chainsKey(/* @__PURE__ */ new Set());
868
944
  settle();
869
- }, [loadCount, settle]);
945
+ }, [
946
+ loadCount,
947
+ sceneVersion,
948
+ settle
949
+ ]);
870
950
  return highlightChain;
871
951
  }
872
952
  //#endregion
873
- //#region src/common/chainColors.ts
874
- /**
875
- * Default colors for telling chains apart, in assignment order.
876
- *
877
- * The Okabe-Ito qualitative palette, which is built to stay distinguishable
878
- * under every common form of color blindness - the reason to prefer it over a
879
- * hand-picked set, since a complex is read by which chain is which and a
880
- * viewer that collapses two of them into one color stops answering that.
881
- *
882
- * Its black is left out: the canvas inverts with the theme, so a chain painted
883
- * black would vanish against the dark background. The rest carry enough
884
- * lightness to hold up against both.
885
- *
886
- * Chains past the end of the palette wrap around to the start.
887
- */
888
- const CHAIN_COLOR_PALETTE = [
889
- "#009E73",
890
- "#0072B2",
891
- "#E69F00",
892
- "#CC79A7",
893
- "#56B4E9",
894
- "#D55E00",
895
- "#F0E442"
896
- ];
897
- /**
898
- * Assigns each chain a color, in the order given, with `overrides` taking
899
- * precedence by `chainId`.
900
- *
901
- * Palette positions are spent on every chain whether or not it is overridden,
902
- * so overriding one chain does not shift the colors of the others.
903
- */
904
- function chainColorMap(chainIds, overrides) {
905
- const colors = /* @__PURE__ */ new Map();
906
- chainIds.forEach((chainId, i) => {
907
- var _overrides$chainId;
908
- const fallback = CHAIN_COLOR_PALETTE[i % CHAIN_COLOR_PALETTE.length];
909
- colors.set(chainId, (_overrides$chainId = overrides === null || overrides === void 0 ? void 0 : overrides[chainId]) !== null && _overrides$chainId !== void 0 ? _overrides$chainId : fallback);
910
- });
911
- return colors;
912
- }
913
- //#endregion
914
953
  //#region src/core/ProteinStructureViewer/hooks/useChains.ts
915
954
  /**
916
955
  * Reconciles the chains found in the structure with the visibility and coloring
@@ -944,7 +983,7 @@ function useChains({ chainColors, chains, hiddenChains, onChainVisibilityChange
944
983
  const hiddenKey = [...isControlled ? hiddenChains : ownHidden].sort().join("\0");
945
984
  const hidden = (0, react.useMemo)(() => new Set(hiddenKey === "" ? [] : hiddenKey.split("\0")), [hiddenKey]);
946
985
  return {
947
- colors: (0, react.useMemo)(() => chainColorMap(chainIds, chainColors), [chainIds.map((id) => {
986
+ colors: (0, react.useMemo)(() => require_renderStructureImage.chainColorMap(chainIds, chainColors), [chainIds.map((id) => {
948
987
  var _chainColors$id;
949
988
  return `${id}:${(_chainColors$id = chainColors === null || chainColors === void 0 ? void 0 : chainColors[id]) !== null && _chainColors$id !== void 0 ? _chainColors$id : ""}`;
950
989
  }).join("\0")]),
@@ -1578,159 +1617,6 @@ function useResidueMarkers({ containerRef, residueColors, sequenceWrapper }) {
1578
1617
  });
1579
1618
  }
1580
1619
  //#endregion
1581
- //#region src/core/ProteinStructureViewer/utils/selection.ts
1582
- /**
1583
- * A stable string for a selection, for comparing one against another.
1584
- *
1585
- * A consumer holding the selection in state hands back a new object on every
1586
- * render, so identity says nothing about whether the selection changed. Order
1587
- * is normalized too: `{ residues: [2, 1] }` and `{ residues: [1, 2] }` select
1588
- * the same residues and must not read as a change.
1589
- */
1590
- function selectionKey(selection) {
1591
- var _selection$residues, _selection$chains;
1592
- const residues = [...new Set((_selection$residues = selection.residues) !== null && _selection$residues !== void 0 ? _selection$residues : [])].sort((a, b) => a - b);
1593
- return `${[...new Set((_selection$chains = selection.chains) !== null && _selection$chains !== void 0 ? _selection$chains : [])].sort().join(",")}|${residues.join(",")}`;
1594
- }
1595
- /** True when the selection names nothing at all. */
1596
- function isEmptySelection(selection) {
1597
- var _selection$residues2, _selection$chains2;
1598
- if (!selection) return true;
1599
- return !((_selection$residues2 = selection.residues) === null || _selection$residues2 === void 0 ? void 0 : _selection$residues2.length) && !((_selection$chains2 = selection.chains) === null || _selection$chains2 === void 0 ? void 0 : _selection$chains2.length);
1600
- }
1601
- /**
1602
- * The residues a selection covers, ascending and without repeats.
1603
- *
1604
- * Chains are expanded through `residuesByChain`, which was read off the loaded
1605
- * structure, so a chain contributes exactly the residues it holds rather than
1606
- * the span between its ends. A residue named directly and also carried by a
1607
- * selected chain is counted once.
1608
- */
1609
- function selectionResidues(selection, residuesByChain) {
1610
- var _selection$residues3, _selection$chains3;
1611
- const residues = new Set((_selection$residues3 = selection.residues) !== null && _selection$residues3 !== void 0 ? _selection$residues3 : []);
1612
- for (const chainId of (_selection$chains3 = selection.chains) !== null && _selection$chains3 !== void 0 ? _selection$chains3 : []) {
1613
- var _residuesByChain$get;
1614
- for (const index of (_residuesByChain$get = residuesByChain.get(chainId)) !== null && _residuesByChain$get !== void 0 ? _residuesByChain$get : []) residues.add(index);
1615
- }
1616
- return [...residues].sort((a, b) => a - b);
1617
- }
1618
- /**
1619
- * Names a selection for the legend's readout.
1620
- *
1621
- * A single chain is named as one, since "Chain A" says more than the number of
1622
- * residues it happens to hold. Anything else is reported as a count, which is
1623
- * the only thing true of every shape a selection can take. A lone residue is
1624
- * named by the caller instead, which reads it back off the structure to get its
1625
- * three-letter code and the number the file gives it.
1626
- */
1627
- function selectionLabel(selection, residueCount) {
1628
- var _selection$chains4, _selection$residues4;
1629
- const chains = (_selection$chains4 = selection.chains) !== null && _selection$chains4 !== void 0 ? _selection$chains4 : [];
1630
- if (chains.length === 1 && !((_selection$residues4 = selection.residues) === null || _selection$residues4 === void 0 ? void 0 : _selection$residues4.length)) return `Chain ${chains[0]}`;
1631
- return `${residueCount} residues`;
1632
- }
1633
- /** The mean of the values `read` returns, ignoring residues without one. */
1634
- function meanOverResidues(residues, read) {
1635
- let sum = 0;
1636
- let count = 0;
1637
- for (const index of residues) {
1638
- const value = read(index);
1639
- if (value === null || value === void 0) continue;
1640
- sum += value;
1641
- count++;
1642
- }
1643
- return count === 0 ? null : sum / count;
1644
- }
1645
- //#endregion
1646
- //#region src/core/ProteinStructureViewer/utils/residueLoci.ts
1647
- /**
1648
- * Every atom the predicate accepts, as geometry Mol* can focus, frame and mark,
1649
- * or undefined when it accepts none.
1650
- *
1651
- * One pass over the structure rather than a pass per residue: a whole-chain
1652
- * selection names hundreds of residues, and resolving them one at a time would
1653
- * walk the structure once for each.
1654
- *
1655
- * Atoms are bucketed by the unit they belong to because element indices are
1656
- * unit-local. That bucketing is also what lets a result span a chain break: it
1657
- * carries an entry per unit, which is what a range across two chains needs and
1658
- * what a single unit-local entry cannot express.
1659
- *
1660
- * Whole residues are collected rather than one atom each, since the caller
1661
- * frames the camera on the loci's bounding sphere and a lone atom would zoom
1662
- * far closer than the residue warrants.
1663
- */
1664
- function lociMatching(structure, matches) {
1665
- const location = molstar_lib_mol_model_structure.StructureElement.Location.create(structure);
1666
- const elements = [];
1667
- for (const unit of structure.units) {
1668
- location.unit = unit;
1669
- const indices = [];
1670
- for (let i = 0; i < unit.elements.length; i++) {
1671
- location.element = unit.elements[i];
1672
- if (matches(location)) indices.push(i);
1673
- }
1674
- if (indices.length > 0) elements.push({
1675
- indices,
1676
- unit
1677
- });
1678
- }
1679
- if (elements.length === 0) return void 0;
1680
- return molstar_lib_mol_model_structure.StructureElement.Loci(structure, elements.map(({ indices, unit }) => ({
1681
- indices: molstar_lib_mol_data_int.OrderedSet.ofSortedArray(indices),
1682
- unit
1683
- })));
1684
- }
1685
- /**
1686
- * Every atom of the residues a selection covers, within one structure.
1687
- *
1688
- * `hiddenChains` is subtracted from the result. A hidden chain is not drawn, so
1689
- * geometry built from it would have nothing to sit on: Mol*'s focus
1690
- * representation draws its own ball-and-stick from whatever is focused, in a
1691
- * part of the state tree the chain's own visibility does not reach, and a
1692
- * hidden chain left in the loci would keep showing up there after its cartoon
1693
- * had gone.
1694
- */
1695
- function lociForSelectionInStructure(structure, selection, hiddenChains) {
1696
- var _selection$residues, _selection$chains;
1697
- if (isEmptySelection(selection)) return void 0;
1698
- const residues = new Set((_selection$residues = selection.residues) !== null && _selection$residues !== void 0 ? _selection$residues : []);
1699
- const chains = new Set((_selection$chains = selection.chains) !== null && _selection$chains !== void 0 ? _selection$chains : []);
1700
- return lociMatching(structure, (location) => {
1701
- const chainId = molstar_lib_mol_model_structure.StructureProperties.chain.auth_asym_id(location);
1702
- if (hiddenChains === null || hiddenChains === void 0 ? void 0 : hiddenChains.has(chainId)) return false;
1703
- return residues.has(molstar_lib_mol_model_structure.StructureProperties.residue.key(location)) || chains.has(chainId);
1704
- });
1705
- }
1706
- /**
1707
- * Every atom of the residues between two 0-based indices, inclusive.
1708
- *
1709
- * A contiguous span is tested by comparison rather than by expanding it into a
1710
- * set of indices: this runs on every residue a drag crosses, and a drag can
1711
- * span the whole structure.
1712
- */
1713
- function lociForResidueRange(structure, min, max) {
1714
- return lociMatching(structure, (location) => {
1715
- const index = molstar_lib_mol_model_structure.StructureProperties.residue.key(location);
1716
- return index >= min && index <= max;
1717
- });
1718
- }
1719
- /**
1720
- * The same, across every structure loaded into a plugin. Results are merged
1721
- * into the first structure that matched: a loci belongs to one structure, and
1722
- * the residue index space already spans them as though they were one.
1723
- */
1724
- function lociForSelection(plugin, selection, hiddenChains) {
1725
- for (const entry of plugin.managers.structure.hierarchy.current.structures) {
1726
- var _entry$cell$obj;
1727
- const structure = (_entry$cell$obj = entry.cell.obj) === null || _entry$cell$obj === void 0 ? void 0 : _entry$cell$obj.data;
1728
- if (!structure) continue;
1729
- const loci = lociForSelectionInStructure(structure, selection, hiddenChains);
1730
- if (loci) return loci;
1731
- }
1732
- }
1733
- //#endregion
1734
1620
  //#region src/core/ProteinStructureViewer/utils/residueRef.ts
1735
1621
  /**
1736
1622
  * Reads the residue a loci points at, or null when it points at nothing the
@@ -1821,7 +1707,7 @@ function extendToRange(loci, anchor) {
1821
1707
  if (from.index === to.index) return loci;
1822
1708
  const min = Math.min(from.index, to.index);
1823
1709
  const max = Math.max(from.index, to.index);
1824
- return (_lociForResidueRange = lociForResidueRange(loci.structure, min, max)) !== null && _lociForResidueRange !== void 0 ? _lociForResidueRange : loci;
1710
+ return (_lociForResidueRange = require_renderStructureImage.lociForResidueRange(loci.structure, min, max)) !== null && _lociForResidueRange !== void 0 ? _lociForResidueRange : loci;
1825
1711
  }
1826
1712
  //#endregion
1827
1713
  //#region src/core/ProteinStructureViewer/components/SequenceView/hooks/useHoveredResidue.ts
@@ -2459,138 +2345,6 @@ function createSequenceView(viewSettings) {
2459
2345
  };
2460
2346
  }
2461
2347
  //#endregion
2462
- //#region src/core/ProteinStructureViewer/utils/axes.ts
2463
- /**
2464
- * The orientation axes widget, as Mol* parameterizes it.
2465
- *
2466
- * Shared rather than declared where it is used, because two different parts of
2467
- * Mol* take the same shape: the live canvas takes it as
2468
- * `camera.helper.axes`, and the screenshot helper takes it as its own `axes`,
2469
- * so an image can carry the widget or leave it out independently of the view
2470
- * it was captured from.
2471
- */
2472
- const AXES_ON = {
2473
- name: "on",
2474
- params: {
2475
- alpha: .51,
2476
- colorX: 16711680,
2477
- colorY: 32768,
2478
- colorZ: 255,
2479
- labelColorX: 8421504,
2480
- labelColorY: 8421504,
2481
- labelColorZ: 8421504,
2482
- labelOpacity: 1,
2483
- labelScale: .25,
2484
- labelX: "X",
2485
- labelY: "Y",
2486
- labelZ: "Z",
2487
- location: "bottom-left",
2488
- locationOffsetX: 0,
2489
- locationOffsetY: 0,
2490
- originColor: 8421504,
2491
- planeColorXY: 8421504,
2492
- planeColorXZ: 8421504,
2493
- planeColorYZ: 8421504,
2494
- radiusScale: .075,
2495
- scale: .15,
2496
- showLabels: false,
2497
- showPlanes: true
2498
- }
2499
- };
2500
- const AXES_OFF = {
2501
- name: "off",
2502
- params: {}
2503
- };
2504
- //#endregion
2505
- //#region src/core/ProteinStructureViewer/utils/color.ts
2506
- /** Parses `#RRGGBB` (or `RRGGBB`) into a Mol* `Color`. */
2507
- function parseHexColor(hex) {
2508
- const h = hex.replace("#", "");
2509
- return molstar_lib_mol_util_color.Color.fromRgb(parseInt(h.substring(0, 2), 16), parseInt(h.substring(2, 4), 16), parseInt(h.substring(4, 6), 16));
2510
- }
2511
- /**
2512
- * Parses a theme color into a Mol* `Color`, falling back when the token is
2513
- * missing or is a format Mol* cannot consume (theme values are hex in practice,
2514
- * but the palette types allow any CSS color string).
2515
- */
2516
- function themeColor(value, fallback) {
2517
- const candidate = value === null || value === void 0 ? void 0 : value.trim();
2518
- return parseHexColor(candidate !== void 0 && /^#?[0-9a-f]{6}$/i.test(candidate) ? candidate : fallback);
2519
- }
2520
- /**
2521
- * Neutral fill for residues with no overlay value. Sits between the light and
2522
- * dark backgrounds so unhighlighted geometry stays legible in both modes.
2523
- */
2524
- function neutralResidueColor(mode) {
2525
- const g = mode === "dark" ? 100 : 180;
2526
- return molstar_lib_mol_util_color.Color.fromRgb(g, g, g);
2527
- }
2528
- //#endregion
2529
- //#region src/core/ProteinStructureViewer/utils/screenshot.ts
2530
- /**
2531
- * Each resolution as the Mol* preset that produces it: 1280x720, 1920x1080,
2532
- * 3840x2160, 7680x4320. Named here rather than given as dimensions so the
2533
- * aspect stays Mol*'s to choose.
2534
- *
2535
- * Mol*'s own `viewport` preset - the canvas at its current size - is left out
2536
- * deliberately: it makes the image depend on how big the viewer happens to be
2537
- * on screen, which is not something a caller asking for "high" is asking for.
2538
- */
2539
- const RESOLUTION_PRESETS = {
2540
- high: "ultra-hd",
2541
- low: "hd",
2542
- maximum: "8k-ultra-hd",
2543
- medium: "full-hd"
2544
- };
2545
- /** Painted behind the structure when a background is asked for but unreadable. */
2546
- const FALLBACK_BACKGROUND = "#ffffff";
2547
- /**
2548
- * Downloads the structure as a PNG.
2549
- *
2550
- * Mol*'s screenshot helper renders its own pass rather than reading the canvas
2551
- * back, so the image is drawn fresh at the requested size and carries its own
2552
- * transparency and axes settings - none of which disturb the view on screen.
2553
- *
2554
- * A named background is the one exception. The helper only chooses between
2555
- * transparent and whatever the renderer is already painting, so a color has to
2556
- * be put on the canvas for the length of the capture and taken off afterwards.
2557
- * `finally` is what guarantees the second half: leaving a figure's background
2558
- * behind on the viewer would be a worse bug than a failed download.
2559
- */
2560
- function downloadStructureImage(_x) {
2561
- return _downloadStructureImage.apply(this, arguments);
2562
- }
2563
- function _downloadStructureImage() {
2564
- _downloadStructureImage = _asyncToGenerator(function* (plugin, options = {}) {
2565
- const helper = plugin.helpers.viewportScreenshot;
2566
- if (!helper) return;
2567
- const { backgroundColor, filename, resolution = "medium", showAxes } = options;
2568
- const transparent = backgroundColor === void 0;
2569
- helper.behaviors.values.next(require_objectSpread2._objectSpread2(require_objectSpread2._objectSpread2({}, helper.behaviors.values.value), {}, {
2570
- axes: showAxes ? AXES_ON : AXES_OFF,
2571
- format: {
2572
- name: "png",
2573
- params: {}
2574
- },
2575
- resolution: {
2576
- name: RESOLUTION_PRESETS[resolution],
2577
- params: {}
2578
- },
2579
- transparent
2580
- }));
2581
- const canvas3d = plugin.canvas3d;
2582
- const painted = canvas3d === null || canvas3d === void 0 ? void 0 : canvas3d.props.renderer.backgroundColor;
2583
- const overrideBackground = !transparent && canvas3d !== void 0;
2584
- if (overrideBackground) canvas3d === null || canvas3d === void 0 || canvas3d.setProps({ renderer: { backgroundColor: themeColor(backgroundColor, FALLBACK_BACKGROUND) } });
2585
- try {
2586
- yield helper.download(filename);
2587
- } finally {
2588
- if (overrideBackground && painted !== void 0) canvas3d === null || canvas3d === void 0 || canvas3d.setProps({ renderer: { backgroundColor: painted } });
2589
- }
2590
- });
2591
- return _downloadStructureImage.apply(this, arguments);
2592
- }
2593
- //#endregion
2594
2348
  //#region src/core/ProteinStructureViewer/components/Viewport/constants.ts
2595
2349
  /**
2596
2350
  * Fraction of the bounding sphere radius within which the camera still counts
@@ -2693,7 +2447,7 @@ function useCameraReset() {
2693
2447
  ]);
2694
2448
  return {
2695
2449
  cameraChanged,
2696
- resetCamera: (0, react.useCallback)(_asyncToGenerator(function* () {
2450
+ resetCamera: (0, react.useCallback)(require_renderStructureImage._asyncToGenerator(function* () {
2697
2451
  yield molstar_lib_mol_plugin_commands.PluginCommands.Camera.Reset(plugin, { durationMs: 250 });
2698
2452
  yield new Promise((resolve) => setTimeout(resolve, 250));
2699
2453
  yield molstar_lib_mol_plugin_commands.PluginCommands.Camera.ResetAxes(plugin, { durationMs: 250 });
@@ -2753,22 +2507,32 @@ function createViewportView(viewSettings) {
2753
2507
  return (_s$showAxes = s.showAxes) !== null && _s$showAxes !== void 0 ? _s$showAxes : true;
2754
2508
  });
2755
2509
  const download = useViewSetting(viewSettings, (s) => s.download);
2510
+ const onError = useViewSetting(viewSettings, (s) => s.onError);
2511
+ const sceneBusy = useViewSetting(viewSettings, (s) => {
2512
+ var _s$sceneBusy;
2513
+ return (_s$sceneBusy = s.sceneBusy) !== null && _s$sceneBusy !== void 0 ? _s$sceneBusy : false;
2514
+ });
2756
2515
  /**
2757
2516
  * Disabled while a capture is in flight. A high resolution takes long
2758
2517
  * enough to click twice through, and each click renders its own pass.
2759
2518
  */
2760
2519
  const [downloading, setDownloading] = (0, react.useState)(false);
2761
- const captureImage = (0, react.useCallback)(_asyncToGenerator(function* () {
2520
+ const captureImage = (0, react.useCallback)(require_renderStructureImage._asyncToGenerator(function* () {
2762
2521
  if (!download) return;
2763
2522
  setDownloading(true);
2764
2523
  try {
2765
- yield downloadStructureImage(plugin, download);
2524
+ yield require_renderStructureImage.exportStructureImage(plugin, download);
2766
2525
  } catch (error) {
2767
- console.error("Failed to download the structure image:", error);
2526
+ if (onError) onError(error, "capture");
2527
+ else console.error("Failed to download the structure image:", error);
2768
2528
  } finally {
2769
2529
  setDownloading(false);
2770
2530
  }
2771
- }), [download, plugin]);
2531
+ }), [
2532
+ download,
2533
+ onError,
2534
+ plugin
2535
+ ]);
2772
2536
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
2773
2537
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(molstar_lib_mol_plugin_ui_viewport.Viewport, {}),
2774
2538
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
@@ -2802,7 +2566,7 @@ function createViewportView(viewSettings) {
2802
2566
  textAlign: "left",
2803
2567
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_czi_sds_components.Button, {
2804
2568
  "aria-label": "Download image of the structure",
2805
- disabled: downloading,
2569
+ disabled: downloading || sceneBusy,
2806
2570
  onClick: captureImage,
2807
2571
  sdsStyle: "minimal",
2808
2572
  sdsType: "secondary",
@@ -2909,150 +2673,6 @@ function syncClipToZoom(plugin, getClipRatio) {
2909
2673
  }
2910
2674
  }
2911
2675
  //#endregion
2912
- //#region src/core/ProteinStructureViewer/utils/chains.ts
2913
- /**
2914
- * The polymer of one chain, as named in the file. What each chain's cartoon is
2915
- * built from, and so what makes a chain a thing Mol* can hide on its own.
2916
- *
2917
- * Paired with `chainLigandExpression`: between them they cover everything on a
2918
- * chain the viewer draws, split by what it draws it as. The polymer
2919
- * restriction is the one Mol*'s `polymer-cartoon` preset applied, and is what
2920
- * keeps a heme from being handed to a representation that can only trace a
2921
- * backbone.
2922
- */
2923
- function chainPolymerExpression(chainId) {
2924
- return molstar_lib_mol_script_language_builder.MolScriptBuilder.struct.generator.atomGroups({
2925
- "chain-test": molstar_lib_mol_script_language_builder.MolScriptBuilder.core.rel.eq([molstar_lib_mol_script_language_builder.MolScriptBuilder.ammp("auth_asym_id"), chainId]),
2926
- "entity-test": molstar_lib_mol_script_language_builder.MolScriptBuilder.core.rel.eq([molstar_lib_mol_script_language_builder.MolScriptBuilder.ammp("entityType"), "polymer"])
2927
- });
2928
- }
2929
- /**
2930
- * Bonds worth following out of a ligand: the ones holding it to the protein.
2931
- *
2932
- * Mol*'s own pair, and metallic coordination is the half that matters most -
2933
- * a heme's iron is held to its histidine by a coordination bond and not a
2934
- * covalent one, so covalent alone would leave every metalloprotein's cofactor
2935
- * looking dropped in rather than bound.
2936
- */
2937
- const CONNECTING_BOND = molstar_lib_mol_script_language_builder.MolScriptBuilder.core.flags.hasAny([molstar_lib_mol_script_language_builder.MolScriptBuilder.struct.bondProperty.flags(), molstar_lib_mol_script_language_builder.MolScriptBuilder.core.type.bitflags([molstar_lib_mol_model_structure_model_types.BondType.fromNames(["covalent", "metal-coordination"])])]);
2938
- /**
2939
- * The heteroatoms of one chain - ligands, ions, glycans, lipids - along with
2940
- * the residues holding them, which is what its ball-and-stick is built from.
2941
- *
2942
- * `non-polymer` is the one entity type a PDB's HETATM records land under,
2943
- * ligands and ions alike, so there is nothing finer to test here: the
2944
- * distinction Mol*'s presets draw between their `ligand`, `ion` and `lipid`
2945
- * components is a residue-name lookup made to give each a slightly different
2946
- * style, and all three are sticks. `branched` is the separate type glycans
2947
- * arrive as.
2948
- *
2949
- * Water is the third type and is left out of the seed. Every solvent molecule
2950
- * drawn as sticks buries the structure they surround, which is why Mol* draws
2951
- * them at 60% alpha rather than plain, and none of them is what "show the
2952
- * ligands" asks for.
2953
- *
2954
- * One layer of bonds out from there, taken as whole residues, is what brings
2955
- * the binding residue along - myoglobin's His93, say. Without it the heme
2956
- * floats unattached in the middle of a cartoon it is visibly bonded to, and
2957
- * the bond drawn to its iron ends in mid-air. One layer and no more: the
2958
- * residue that holds the ligand, not that residue's own neighbours.
2959
- *
2960
- * Intersected back with the chain, so everything a chain is drawn with belongs
2961
- * to that chain. A ligand bonded across an interface would otherwise leave the
2962
- * partner chain's residue in this chain's component, somewhere hiding the
2963
- * partner chain could not reach.
2964
- */
2965
- function chainLigandExpression(chainId) {
2966
- const onThisChain = molstar_lib_mol_script_language_builder.MolScriptBuilder.core.rel.eq([molstar_lib_mol_script_language_builder.MolScriptBuilder.ammp("auth_asym_id"), chainId]);
2967
- return molstar_lib_mol_script_language_builder.MolScriptBuilder.struct.modifier.union([molstar_lib_mol_script_language_builder.MolScriptBuilder.struct.modifier.intersectBy({
2968
- 0: molstar_lib_mol_script_language_builder.MolScriptBuilder.struct.modifier.includeConnected({
2969
- 0: molstar_lib_mol_script_language_builder.MolScriptBuilder.struct.generator.atomGroups({
2970
- "chain-test": onThisChain,
2971
- "entity-test": molstar_lib_mol_script_language_builder.MolScriptBuilder.core.set.has([molstar_lib_mol_script_language_builder.MolScriptBuilder.set("non-polymer", "branched"), molstar_lib_mol_script_language_builder.MolScriptBuilder.ammp("entityType")])
2972
- }),
2973
- "as-whole-residues": true,
2974
- "bond-test": CONNECTING_BOND,
2975
- "layer-count": 1
2976
- }),
2977
- by: molstar_lib_mol_script_language_builder.MolScriptBuilder.struct.generator.atomGroups({ "chain-test": onThisChain })
2978
- })]);
2979
- }
2980
- /**
2981
- * Matches how the sequence panel captions its chains, which reaches the same
2982
- * label through Mol*'s own `getChainOptions`. Deriving it the same way is what
2983
- * keeps a chain from going by one name in the legend and another in the panel.
2984
- */
2985
- const CHAIN_LABEL_OPTIONS = {
2986
- granularity: "chain",
2987
- hidePrefix: true,
2988
- htmlStyling: false
2989
- };
2990
- /**
2991
- * Reads the chains of a structure and the polymer residues on each.
2992
- *
2993
- * Residue indices are Mol*'s residue keys, counting residues in file order
2994
- * across the whole structure, so what is reported here addresses the same
2995
- * residues as `plddt`, `residueOverlay` and `selection`.
2996
- *
2997
- * Heteroatoms are drawn (see `chainLigandExpression`) but are not counted
2998
- * here. A heme is a residue to the file and not to a reader: it is no part of
2999
- * the sequence the panel lists, and averaging a confidence score over it would
3000
- * divide by a residue that has none.
3001
- *
3002
- * Residues are gathered into a set rather than inferred from the ends: a chain
3003
- * carrying several symmetry operators repeats each of its residues once per
3004
- * operator, and a structure whose chains interleave leaves a chain's indices
3005
- * non-contiguous. Neither is what a predicted complex looks like, but a range
3006
- * would misread both.
3007
- */
3008
- function scanChains(structure) {
3009
- const accumulators = /* @__PURE__ */ new Map();
3010
- const location = molstar_lib_mol_model_structure.StructureElement.Location.create(structure);
3011
- for (const unit of structure.units) {
3012
- location.unit = unit;
3013
- for (let i = 0; i < unit.elements.length; i++) {
3014
- location.element = unit.elements[i];
3015
- const chainId = molstar_lib_mol_model_structure.StructureProperties.chain.auth_asym_id(location);
3016
- let chain = accumulators.get(chainId);
3017
- if (!chain) {
3018
- chain = {
3019
- chainId,
3020
- label: (0, molstar_lib_mol_theme_label.elementLabel)(location, CHAIN_LABEL_OPTIONS),
3021
- residues: /* @__PURE__ */ new Set()
3022
- };
3023
- accumulators.set(chainId, chain);
3024
- }
3025
- if (molstar_lib_mol_model_structure.StructureProperties.entity.type(location) !== "polymer") continue;
3026
- chain.residues.add(molstar_lib_mol_model_structure.StructureProperties.residue.key(location));
3027
- }
3028
- }
3029
- const ordered = [...accumulators.values()].filter((chain) => chain.residues.size > 0).map((chain) => require_objectSpread2._objectSpread2(require_objectSpread2._objectSpread2({}, chain), {}, { residues: [...chain.residues].sort((a, b) => a - b) })).sort((a, b) => {
3030
- var _a$residues$, _b$residues$;
3031
- return ((_a$residues$ = a.residues[0]) !== null && _a$residues$ !== void 0 ? _a$residues$ : 0) - ((_b$residues$ = b.residues[0]) !== null && _b$residues$ !== void 0 ? _b$residues$ : 0);
3032
- });
3033
- return {
3034
- chainLabels: new Map([...accumulators.values()].map(({ chainId, label }) => [chainId, label])),
3035
- chains: ordered.map(({ chainId, label, residues }) => {
3036
- var _residues, _residues$;
3037
- return {
3038
- chainId,
3039
- endIndex: (_residues = residues[residues.length - 1]) !== null && _residues !== void 0 ? _residues : 0,
3040
- label,
3041
- residueCount: residues.length,
3042
- startIndex: (_residues$ = residues[0]) !== null && _residues$ !== void 0 ? _residues$ : 0
3043
- };
3044
- }),
3045
- residuesByChain: new Map(ordered.map(({ chainId, residues }) => [chainId, residues]))
3046
- };
3047
- }
3048
- /** True when the two lists name the same chains, in the same order. */
3049
- function chainsEqual(a, b) {
3050
- return a.length === b.length && a.every((chain, i) => {
3051
- const other = b[i];
3052
- return other !== void 0 && chain.chainId === other.chainId && chain.startIndex === other.startIndex && chain.endIndex === other.endIndex && chain.residueCount === other.residueCount;
3053
- });
3054
- }
3055
- //#endregion
3056
2676
  //#region src/core/ProteinStructureViewer/utils/molstarSpec.ts
3057
2677
  /**
3058
2678
  * The spec's list-valued keys. These are appended rather than merged: Mol*
@@ -3120,421 +2740,16 @@ function mergeMolstarSpec(base, override) {
3120
2740
  return merged;
3121
2741
  }
3122
2742
  //#endregion
3123
- //#region src/core/ProteinStructureViewer/utils/structureFormat.ts
3124
- /** How much of the file to inspect when guessing the format. */
3125
- const DETECT_SAMPLE = 16384;
3126
- /**
3127
- * Infers whether `text` is PDB or mmCIF (PDBx).
3128
- *
3129
- * mmCIF documents start with a `data_` block and name their coordinates
3130
- * `_atom_site.`. PDB never does either, so either marker is enough. A leading
3131
- * comment (`# ...`) before `data_` is still mmCIF.
3132
- */
3133
- function detectStructureFormat(text) {
3134
- const sample = text.slice(0, DETECT_SAMPLE);
3135
- if (/(?:^|\n)\s*data_/.test(sample) || /_atom_site\./.test(sample)) return "mmcif";
3136
- return "pdb";
3137
- }
3138
- //#endregion
3139
- //#region src/core/ProteinStructureViewer/utils/chainColorTheme.ts
3140
- /** Mol* color theme name under which per-chain coloring is registered. */
3141
- const CHAIN_COLOR_THEME_NAME = "chain-color";
3142
- /** Painted for a chain with no color assigned, or one assigned a bad value. */
3143
- const DEFAULT_CHAIN_COLOR_HEX = "#808080";
3144
- const DEFAULT_CHAIN_COLOR = molstar_lib_mol_util_color.Color.fromRgb(128, 128, 128);
3145
- /**
3146
- * Builds a Mol* color theme that paints each chain its own color.
3147
- *
3148
- * This stands in for Mol*'s built-in `chain-id` theme, which picks its own
3149
- * colors from a palette we cannot read back. Owning the assignment is what
3150
- * lets the chain legend show a swatch that matches the structure, rather than
3151
- * a key for colors it has to guess at.
3152
- *
3153
- * Created per plugin instance, and its state read live inside `color()`, for
3154
- * the same reasons as the residue value theme beside it: several viewers can
3155
- * color chains differently on one page, and recoloring does not rebuild the
3156
- * structure.
3157
- */
3158
- function createChainColorTheme() {
3159
- const state = {
3160
- colors: /* @__PURE__ */ new Map(),
3161
- version: 0
3162
- };
3163
- const provider = {
3164
- category: "chain property",
3165
- defaultValues: {},
3166
- factory() {
3167
- const { colors } = state;
3168
- const parsed = /* @__PURE__ */ new Map();
3169
- for (const [chainId, hex] of colors) parsed.set(chainId, themeColor(hex, DEFAULT_CHAIN_COLOR_HEX));
3170
- return {
3171
- color(location) {
3172
- var _parsed$get;
3173
- if (!molstar_lib_mol_model_structure.StructureElement.Location.is(location)) return DEFAULT_CHAIN_COLOR;
3174
- const chainId = molstar_lib_mol_model_structure.StructureProperties.chain.auth_asym_id(location);
3175
- return (_parsed$get = parsed.get(chainId)) !== null && _parsed$get !== void 0 ? _parsed$get : DEFAULT_CHAIN_COLOR;
3176
- },
3177
- contextHash: state.version,
3178
- description: "Color residues by the chain they sit on",
3179
- factory: provider.factory,
3180
- granularity: "group",
3181
- props: {}
3182
- };
3183
- },
3184
- getParams: () => ({}),
3185
- isApplicable: () => true,
3186
- label: "Chain Color",
3187
- name: CHAIN_COLOR_THEME_NAME
3188
- };
3189
- return {
3190
- provider,
3191
- setState(colors) {
3192
- state.colors = colors;
3193
- state.version++;
3194
- }
3195
- };
3196
- }
3197
- //#endregion
3198
- //#region src/core/ProteinStructureViewer/utils/plddt.ts
3199
- /** Mol* color theme name under which pLDDT coloring is registered. */
3200
- const PLDDT_THEME_NAME = "plddt-bfactor";
3201
- /**
3202
- * AlphaFold's pLDDT confidence bands. Thresholds are on the 0-1 scale that
3203
- * `plddt` values use, ascending from lowest to highest confidence.
3204
- */
3205
- const PLDDT_COLOR_SCALE = {
3206
- kind: "stepped",
3207
- stops: [
3208
- {
3209
- color: "#FF7C45",
3210
- label: "0.5",
3211
- threshold: .5
3212
- },
3213
- {
3214
- color: "#FFDB11",
3215
- label: "0.7",
3216
- threshold: .7
3217
- },
3218
- {
3219
- color: "#64CBF3",
3220
- label: "0.9",
3221
- threshold: .9
3222
- },
3223
- {
3224
- color: "#0053D5",
3225
- label: "1.0",
3226
- threshold: 1
3227
- }
3228
- ]
3229
- };
3230
- const PLDDT_BANDS = PLDDT_COLOR_SCALE.kind === "stepped" ? PLDDT_COLOR_SCALE.stops : [];
3231
- /**
3232
- * The band colors alone, ascending from least to most confident.
3233
- *
3234
- * What the chain legend shows in place of a single color while pLDDT is
3235
- * painting the structure: no one color stands for a chain then, so the swatch
3236
- * carries the whole key instead.
3237
- */
3238
- const PLDDT_BAND_COLORS = PLDDT_BANDS.map((stop) => stop.color);
3239
- const DEFAULT_PLDDT_COLOR = molstar_lib_mol_util_color.Color.fromRgb(128, 128, 128);
3240
- /**
3241
- * Maps a pLDDT confidence score on the 0-100 B-factor scale to its band color.
3242
- * `PLDDT_COLOR_SCALE` thresholds are on the 0-1 scale, so the score is
3243
- * normalized before it is sampled.
3244
- */
3245
- function plddtToColor(value) {
3246
- const [r, g, b] = require_colorScales.sampleSteppedScale(PLDDT_BANDS, value / 100);
3247
- return molstar_lib_mol_util_color.Color.fromRgb(r, g, b);
3248
- }
3249
- /**
3250
- * Rewrites a PDB file's B-factor column with pLDDT scores so Mol* can read them
3251
- * off the parsed model. B-factors occupy columns 60-66 of ATOM/HETATM lines;
3252
- * scores arrive on a 0-1 scale and are stored on the conventional 0-100 one.
3253
- * Residues past the end of `plddtValues` fall back to a mid-confidence 50.
3254
- *
3255
- * A residue is identified by chain, sequence number and insertion code. The
3256
- * code has to be part of that: `10` and `10A` are two residues, Mol* counts
3257
- * them as two, and treating them as one here would hand every residue after
3258
- * them its neighbour's score.
3259
- */
3260
- function injectPlddtIntoPdb(pdbData, plddtValues) {
3261
- const lines = pdbData.split("\n");
3262
- let residueIndex = -1;
3263
- let lastResidue = "";
3264
- return lines.map((line) => {
3265
- var _plddtValues$residueI;
3266
- if (!line.startsWith("ATOM") && !line.startsWith("HETATM")) return line;
3267
- const residue = line.substring(21, 27);
3268
- if (residue !== lastResidue) {
3269
- residueIndex++;
3270
- lastResidue = residue;
3271
- }
3272
- const bfactorStr = (residueIndex < plddtValues.length ? ((_plddtValues$residueI = plddtValues[residueIndex]) !== null && _plddtValues$residueI !== void 0 ? _plddtValues$residueI : 0) * 100 : 50).toFixed(2).padStart(6, " ");
3273
- const paddedLine = line.padEnd(66, " ");
3274
- return paddedLine.substring(0, 60) + bfactorStr + paddedLine.substring(66);
3275
- }).join("\n");
3276
- }
3277
- const ATOM_SITE_PREFIX = "_atom_site.";
3278
- const B_FACTOR_COLUMN = "B_iso_or_equiv";
3279
- function skipCifWhitespace(line, index) {
3280
- let i = index;
3281
- while (i < line.length && /\s/.test(line.charAt(i))) i += 1;
3282
- return i;
3283
- }
3284
- function readCifToken(line, start) {
3285
- const quote = line.charAt(start);
3286
- if (quote === "'" || quote === "\"") {
3287
- const end = line.indexOf(quote, start + 1);
3288
- if (end < 0) return {
3289
- next: line.length,
3290
- token: line.slice(start + 1)
3291
- };
3292
- return {
3293
- next: end + 1,
3294
- token: line.slice(start + 1, end)
3295
- };
3296
- }
3297
- let i = start;
3298
- while (i < line.length && !/\s/.test(line.charAt(i))) i += 1;
3299
- return {
3300
- next: i,
3301
- token: line.slice(start, i)
3302
- };
3303
- }
3304
- /**
3305
- * Splits one CIF line into tokens, honouring `'quoted'` and `"quoted"` values.
3306
- * Atom-site rows are one line each in practice; multiline `;text;` fields are
3307
- * not used there and are not handled.
3308
- */
3309
- function tokenizeCifLine(line) {
3310
- const tokens = [];
3311
- let i = skipCifWhitespace(line, 0);
3312
- while (i < line.length) {
3313
- const { next, token } = readCifToken(line, i);
3314
- tokens.push(token);
3315
- i = skipCifWhitespace(line, next);
3316
- }
3317
- return tokens;
3318
- }
3319
- function isCifNull(value) {
3320
- return value === void 0 || value === "." || value === "?";
3321
- }
3322
- function columnIndex(columns, name) {
3323
- return columns.indexOf(name);
3324
- }
3325
- /**
3326
- * Residue identity matching `injectPlddtIntoPdb`: chain + sequence number +
3327
- * insertion code. Prefers the `auth_*` columns Mol* reports on the parsed
3328
- * model, falling back to `label_*` when a file omitted them. `.` / `?`
3329
- * insertion codes count as empty, the way a blank PDB column 27 does.
3330
- */
3331
- function mmcifResidueKey(tokens, columns) {
3332
- var _ref, _ref2, _ref3, _ref4;
3333
- const authAsym = columnIndex(columns, "auth_asym_id");
3334
- const labelAsym = columnIndex(columns, "label_asym_id");
3335
- const authSeq = columnIndex(columns, "auth_seq_id");
3336
- const labelSeq = columnIndex(columns, "label_seq_id");
3337
- const ins = columnIndex(columns, "pdbx_PDB_ins_code");
3338
- return `${(_ref = (_ref2 = authAsym >= 0 ? tokens[authAsym] : void 0) !== null && _ref2 !== void 0 ? _ref2 : labelAsym >= 0 ? tokens[labelAsym] : void 0) !== null && _ref !== void 0 ? _ref : ""}|${(_ref3 = (_ref4 = authSeq >= 0 ? tokens[authSeq] : void 0) !== null && _ref4 !== void 0 ? _ref4 : labelSeq >= 0 ? tokens[labelSeq] : void 0) !== null && _ref3 !== void 0 ? _ref3 : ""}|${isCifNull(ins >= 0 ? tokens[ins] : void 0) ? "" : tokens[ins]}`;
3339
- }
3340
- function isAtomSiteRow(tokens, columns) {
3341
- const group = columnIndex(columns, "group_PDB");
3342
- if (group < 0) return true;
3343
- const value = tokens[group];
3344
- return value === "ATOM" || value === "HETATM";
3345
- }
3346
- function plddtBFactor(residueIndex, plddtValues) {
3347
- var _plddtValues$residueI2;
3348
- return (residueIndex < plddtValues.length ? ((_plddtValues$residueI2 = plddtValues[residueIndex]) !== null && _plddtValues$residueI2 !== void 0 ? _plddtValues$residueI2 : 0) * 100 : 50).toFixed(2);
3349
- }
3350
- function isLoopStart(line) {
3351
- return line.trim() === "loop_" || line.trim().startsWith("loop_");
3352
- }
3353
- function isAtomSiteColumn(line) {
3354
- return line.trim().startsWith(ATOM_SITE_PREFIX);
3355
- }
3356
- function endsAtomSiteLoop(line) {
3357
- const trimmed = line.trim();
3358
- if (trimmed === "") return false;
3359
- if (trimmed.startsWith("#")) return true;
3360
- if (isLoopStart(trimmed)) return true;
3361
- if (trimmed.startsWith("data_") || trimmed.startsWith("save_")) return true;
3362
- return trimmed.startsWith("_") && !trimmed.startsWith(ATOM_SITE_PREFIX);
3363
- }
3364
- function isAtomSiteLoop(lines, loopIndex) {
3365
- let peek = loopIndex + 1;
3366
- while (peek < lines.length && lines[peek].trim() === "") peek += 1;
3367
- return peek < lines.length && isAtomSiteColumn(lines[peek]);
3368
- }
3369
- function ensureBFactorColumn(columns, headerLines) {
3370
- const existing = columnIndex(columns, B_FACTOR_COLUMN);
3371
- if (existing >= 0) return existing;
3372
- columns.push(B_FACTOR_COLUMN);
3373
- headerLines.push(`${ATOM_SITE_PREFIX}${B_FACTOR_COLUMN}`);
3374
- return columns.length - 1;
3375
- }
3376
- function rewriteAtomSiteRow(dataLine, columns, bIndex, plddtValues, walk) {
3377
- if (dataLine.trim() === "") return dataLine;
3378
- const tokens = tokenizeCifLine(dataLine);
3379
- if (tokens.length === 0) return dataLine;
3380
- while (tokens.length < columns.length) tokens.push(".");
3381
- if (isAtomSiteRow(tokens, columns)) {
3382
- const residue = mmcifResidueKey(tokens, columns);
3383
- if (residue !== walk.lastResidue) {
3384
- walk.residueIndex += 1;
3385
- walk.lastResidue = residue;
3386
- }
3387
- tokens[bIndex] = plddtBFactor(walk.residueIndex, plddtValues);
3388
- }
3389
- return tokens.join(" ");
3390
- }
3391
- function rewriteAtomSiteLoop(lines, loopIndex, plddtValues, walk) {
3392
- const rows = [lines[loopIndex]];
3393
- let i = loopIndex + 1;
3394
- const columns = [];
3395
- while (i < lines.length && isAtomSiteColumn(lines[i])) {
3396
- columns.push(lines[i].trim().slice(11));
3397
- rows.push(lines[i]);
3398
- i += 1;
3399
- }
3400
- const bIndex = ensureBFactorColumn(columns, rows);
3401
- while (i < lines.length && !endsAtomSiteLoop(lines[i])) {
3402
- rows.push(rewriteAtomSiteRow(lines[i], columns, bIndex, plddtValues, walk));
3403
- i += 1;
3404
- }
3405
- return {
3406
- next: i,
3407
- rows
3408
- };
3409
- }
3410
- /**
3411
- * Rewrites an mmCIF (PDBx) file's `_atom_site.B_iso_or_equiv` column with
3412
- * pLDDT scores so Mol* can read them off the parsed model, the same way
3413
- * `injectPlddtIntoPdb` rewrites the PDB B-factor column. Scores arrive on a
3414
- * 0-1 scale and are stored on the conventional 0-100 one. Residues past the
3415
- * end of `plddtValues` fall back to a mid-confidence 50.
3416
- *
3417
- * If the file has no B-factor column, one is appended to the atom-site loop
3418
- * rather than leaving the scores behind.
3419
- */
3420
- function injectPlddtIntoMmcif(mmcifData, plddtValues) {
3421
- const lines = mmcifData.split("\n");
3422
- const result = [];
3423
- const walk = {
3424
- lastResidue: "",
3425
- residueIndex: -1
3426
- };
3427
- let i = 0;
3428
- let rewrittenLoop = false;
3429
- while (i < lines.length) {
3430
- const line = lines[i];
3431
- if (!rewrittenLoop && isLoopStart(line) && isAtomSiteLoop(lines, i)) {
3432
- const rewritten = rewriteAtomSiteLoop(lines, i, plddtValues, walk);
3433
- result.push(...rewritten.rows);
3434
- i = rewritten.next;
3435
- rewrittenLoop = true;
3436
- continue;
3437
- }
3438
- result.push(line);
3439
- i += 1;
3440
- }
3441
- return result.join("\n");
3442
- }
3443
- /**
3444
- * Rewrites a structure file's B-factors with pLDDT scores. Dispatches to
3445
- * `injectPlddtIntoPdb` or `injectPlddtIntoMmcif` from `format`, or from
3446
- * detecting the format of `data` when `format` is omitted.
3447
- */
3448
- function injectPlddt(data, plddtValues, format = detectStructureFormat(data)) {
3449
- return format === "mmcif" ? injectPlddtIntoMmcif(data, plddtValues) : injectPlddtIntoPdb(data, plddtValues);
3450
- }
3451
- /**
3452
- * Mol* color theme that reads pLDDT scores out of the B-factor column, paired
3453
- * with `injectPlddt`. Registered per plugin instance.
3454
- */
3455
- const PlddtColoring = molstar_lib_mol_model_props_common_custom_element_property.CustomElementProperty.create({
3456
- coloring: {
3457
- defaultColor: DEFAULT_PLDDT_COLOR,
3458
- getColor(e) {
3459
- return plddtToColor(e);
3460
- }
3461
- },
3462
- getData(model) {
3463
- const map = /* @__PURE__ */ new Map();
3464
- const atomCount = model.atomicHierarchy.atoms._rowCount;
3465
- for (let i = 0; i < atomCount; i++) map.set(i, model.atomicConformation.B_iso_or_equiv.value(i));
3466
- return { value: map };
3467
- },
3468
- label: "pLDDT",
3469
- name: PLDDT_THEME_NAME
3470
- });
3471
- //#endregion
3472
- //#region src/core/ProteinStructureViewer/utils/residueValueTheme.ts
3473
- /** Mol* color theme name under which residue value overlays are registered. */
3474
- const RESIDUE_VALUE_THEME_NAME = "residue-value";
3475
- /**
3476
- * Builds a Mol* color theme that paints residues by an arbitrary per-residue
3477
- * value.
3478
- *
3479
- * The theme is created per plugin instance rather than shared at module scope,
3480
- * so several viewers can render different overlays on the same page. Its state
3481
- * lives in the closure below and is read live inside `color()`, which is what
3482
- * lets an overlay update recolor the structure without rebuilding it.
3483
- */
3484
- function createResidueValueTheme(mode) {
3485
- const state = {
3486
- colorScale: require_colorScales.PLASMA_COLOR_SCALE,
3487
- max: 1,
3488
- min: 0,
3489
- mode,
3490
- values: /* @__PURE__ */ new Map(),
3491
- version: 0
3492
- };
3493
- const provider = {
3494
- category: "residue property",
3495
- defaultValues: {},
3496
- factory() {
3497
- const { colorScale, max, min, mode: currentMode, values } = state;
3498
- const neutral = neutralResidueColor(currentMode);
3499
- return {
3500
- color(location) {
3501
- if (!molstar_lib_mol_model_structure.StructureElement.Location.is(location)) return neutral;
3502
- const value = values.get(molstar_lib_mol_model_structure.StructureProperties.residue.key(location));
3503
- if (value === void 0) return neutral;
3504
- const rgb = require_colorScales.sampleColorScale(colorScale, value, max, min);
3505
- return rgb ? molstar_lib_mol_util_color.Color.fromRgb(rgb[0], rgb[1], rgb[2]) : neutral;
3506
- },
3507
- contextHash: state.version,
3508
- description: "Color residues by a per-residue value",
3509
- factory: provider.factory,
3510
- granularity: "group",
3511
- props: {}
3512
- };
3513
- },
3514
- getParams: () => ({}),
3515
- isApplicable: () => true,
3516
- label: "Residue Value",
3517
- name: RESIDUE_VALUE_THEME_NAME
3518
- };
3519
- return {
3520
- provider,
3521
- setState(next) {
3522
- Object.assign(state, next);
3523
- state.version++;
3524
- }
3525
- };
3526
- }
3527
- //#endregion
3528
2743
  //#region src/core/ProteinStructureViewer/hooks/useMolstarPlugin.ts
3529
- /**
3530
- * Theme used when neither pLDDT scores nor an overlay are supplied. Our own
3531
- * per-chain theme rather than Mol*'s built-in `chain-id`, so the chain legend
3532
- * can show swatches that match the structure instead of guessing at the colors
3533
- * Mol* picked.
3534
- */
3535
- const FALLBACK_THEME_NAME = CHAIN_COLOR_THEME_NAME;
3536
2744
  /** Delay before retrying initialization while the container has no size. */
3537
2745
  const LAYOUT_RETRY_MS = 100;
2746
+ /** What the viewer logs for a failure when no `onError` is listening. */
2747
+ const UNHANDLED_ERROR_MESSAGES = {
2748
+ capture: "Failed to download the structure image:",
2749
+ init: "Failed to initialize Mol* viewer:",
2750
+ load: "Failed to load structure:",
2751
+ representation: "Failed to draw the structure:"
2752
+ };
3538
2753
  /** How long to wait for a frame before starting without one. */
3539
2754
  const FRAME_WAIT_MS = 100;
3540
2755
  /**
@@ -3585,7 +2800,7 @@ const SCREENSHOT_CONTROLS_CONFIG = (() => {
3585
2800
  function setAxes(plugin, enabled) {
3586
2801
  if (!plugin.canvas3d) return;
3587
2802
  try {
3588
- plugin.canvas3d.setProps({ camera: { helper: { axes: enabled ? AXES_ON : AXES_OFF } } });
2803
+ plugin.canvas3d.setProps({ camera: { helper: { axes: enabled ? require_renderStructureImage.AXES_ON : require_renderStructureImage.AXES_OFF } } });
3589
2804
  } catch (e) {
3590
2805
  console.warn("Could not set axes:", e);
3591
2806
  }
@@ -3594,7 +2809,7 @@ function createViewer(_x) {
3594
2809
  return _createViewer.apply(this, arguments);
3595
2810
  }
3596
2811
  function _createViewer() {
3597
- _createViewer = _asyncToGenerator(function* ({ backgroundColor, chainColorTheme, edgeColor, highlightColor, molstarSpec, residueValueTheme, root, showAxes, showSequenceViewer, viewSettings }) {
2812
+ _createViewer = require_renderStructureImage._asyncToGenerator(function* ({ backgroundColor, edgeColor, highlightColor, molstarSpec, root, showAxes, showSequenceViewer, viewSettings }) {
3598
2813
  var _spec$config;
3599
2814
  const spec = (0, molstar_lib_mol_plugin_ui_spec.DefaultPluginUISpec)();
3600
2815
  const behaviors = spec.behaviors.filter((b) => b.transformer !== molstar_lib_mol_plugin_behavior.PluginBehaviors.Camera.FocusLoci);
@@ -3603,7 +2818,7 @@ function _createViewer() {
3603
2818
  spec: mergeMolstarSpec(require_objectSpread2._objectSpread2(require_objectSpread2._objectSpread2({}, spec), {}, {
3604
2819
  behaviors,
3605
2820
  canvas3d: {
3606
- camera: { helper: { axes: showAxes ? AXES_ON : AXES_OFF } },
2821
+ camera: { helper: { axes: showAxes ? require_renderStructureImage.AXES_ON : require_renderStructureImage.AXES_OFF } },
3607
2822
  marking: {
3608
2823
  highlightEdgeColor: edgeColor,
3609
2824
  selectEdgeColor: edgeColor
@@ -3649,10 +2864,6 @@ function _createViewer() {
3649
2864
  }), molstarSpec),
3650
2865
  target: root
3651
2866
  });
3652
- const registry = plugin.representation.structure.themes.colorThemeRegistry;
3653
- if (PlddtColoring.colorThemeProvider) registry.add(PlddtColoring.colorThemeProvider);
3654
- registry.add(residueValueTheme.provider);
3655
- registry.add(chainColorTheme.provider);
3656
2867
  for (const p of [...plugin.managers.lociLabels.providers]) plugin.managers.lociLabels.removeProvider(p);
3657
2868
  plugin.managers.structure.focus.behaviors.current.subscribe((entry) => {
3658
2869
  const select = plugin.managers.interactivity.lociSelects;
@@ -3664,232 +2875,59 @@ function _createViewer() {
3664
2875
  return _createViewer.apply(this, arguments);
3665
2876
  }
3666
2877
  /**
3667
- * The representation parameters Mol*'s own presets compute, read from the same
3668
- * manager state they read. Ported rather than hardcoded so a chain drawn here
3669
- * looks exactly like the one the preset used to draw. Mol* hands the same set
3670
- * to its cartoons and its ball-and-sticks, and so does this.
3671
- */
3672
- function representationTypeParams(plugin) {
3673
- const { hydrogens, ignoreLight, visualQuality } = plugin.managers.structure.component.state.options;
3674
- return {
3675
- ignoreHydrogens: hydrogens !== "all",
3676
- ignoreHydrogensVariant: hydrogens === "only-polar" ? "non-polar" : "all",
3677
- ignoreLight,
3678
- quality: visualQuality
3679
- };
3680
- }
3681
- /**
3682
- * The two halves of a chain the viewer draws. Each names the component its
3683
- * half is built under and tags the representation drawn over it, and it is
3684
- * that tag `applyColorTheme` reads back to tell the two apart.
3685
- */
3686
- const POLYMER_PART = "polymer";
3687
- const LIGAND_PART = "ligand";
3688
- /**
3689
- * True for the representation drawing a chain's heteroatoms.
3690
- *
3691
- * Read off the tag put on the representation when it was built, rather than
3692
- * off the component's key. Mol* does not store the key as it was given: it
3693
- * files components under `structure-component-<key>`, so a test against the
3694
- * key as written here silently matches nothing, and every ligand quietly takes
3695
- * the structure-wide theme instead. The tag survives untouched.
3696
- */
3697
- function isLigandRepresentation(representation) {
3698
- var _representation$cell$;
3699
- return ((_representation$cell$ = representation.cell.transform.tags) === null || _representation$cell$ === void 0 ? void 0 : _representation$cell$.includes(LIGAND_PART)) === true;
3700
- }
3701
- /**
3702
- * How heteroatoms are colored: the theme Mol* puts on a ball-and-stick when
3703
- * left to itself, with the parameters it chooses too.
3704
- *
3705
- * Named rather than left blank only so that it survives `applyColorTheme`,
3706
- * which would otherwise repaint the ligands along with everything else. The
3707
- * structure-wide theme is the wrong answer for them twice over: a heme reads
3708
- * as a heme because its iron is orange and its nitrogens are blue, which one
3709
- * flat color throws away, and pLDDT and residue overlays have no value for a
3710
- * HETATM to be colored by at all.
3711
- */
3712
- const ELEMENT_THEME = "element-symbol";
3713
- /**
3714
- * Draws one chain: its polymer as a cartoon, its ligands and ions as
3715
- * ball-and-stick.
3716
- *
3717
- * Two components rather than one, because a component is what a representation
3718
- * attaches to and neither representation suits both halves - a cartoon has no
3719
- * backbone to trace through a heme, and a ball-and-stick over the whole chain
3720
- * would draw every protein atom as a sphere.
3721
- *
3722
- * Hands back the refs of whichever components the chain turned out to have,
3723
- * which is what `setSubtreeVisibility` is later pointed at. Grouping them
3724
- * under the chain is what makes hiding a chain take its ligands with it.
3725
- */
3726
- function buildChainComponents(_x5, _x6, _x7, _x8) {
3727
- return _buildChainComponents.apply(this, arguments);
3728
- }
3729
- function _buildChainComponents() {
3730
- _buildChainComponents = _asyncToGenerator(function* (plugin, structure, chain, typeParams) {
3731
- const builders = plugin.builders.structure;
3732
- const refs = [];
3733
- const addPart = function() {
3734
- var _ref = _asyncToGenerator(function* (part, expression, props) {
3735
- const component = yield builders.tryCreateComponentFromExpression(structure, expression, `${part}-${chain.chainId}`, { label: chain.label });
3736
- if (!component) return;
3737
- yield builders.representation.addRepresentation(component, props, { tag: part });
3738
- refs.push(component.ref);
3739
- });
3740
- return function addPart(_x2, _x3, _x4) {
3741
- return _ref.apply(this, arguments);
3742
- };
3743
- }();
3744
- yield addPart(POLYMER_PART, chainPolymerExpression(chain.chainId), {
3745
- type: "cartoon",
3746
- typeParams
3747
- });
3748
- yield addPart(LIGAND_PART, chainLigandExpression(chain.chainId), {
3749
- color: ELEMENT_THEME,
3750
- type: "ball-and-stick",
3751
- typeParams
3752
- });
3753
- return refs;
3754
- });
3755
- return _buildChainComponents.apply(this, arguments);
3756
- }
3757
- const NOTHING_LOADED = {
3758
- chains: [],
3759
- componentRefs: /* @__PURE__ */ new Map(),
3760
- residuesByChain: /* @__PURE__ */ new Map()
3761
- };
3762
- /**
3763
- * Parses the structure and draws each chain: a cartoon over its polymer, and
3764
- * ball-and-stick over its ligands and ions.
3765
- *
3766
- * Mol*'s `default` hierarchy preset would be shorter, but it groups every
3767
- * polymer chain into a single component, and a component is the unit Mol* can
3768
- * hide. Building them per chain is what makes a chain individually hideable;
3769
- * the refs handed back are what `setSubtreeVisibility` is later pointed at.
3770
- *
3771
- * The wrapper detects PDB vs mmCIF from the text; this just forwards that
3772
- * guess to Mol*.
3773
- */
3774
- function loadStructure(_x9, _x10, _x11, _x12) {
3775
- return _loadStructure.apply(this, arguments);
3776
- }
3777
- function _loadStructure() {
3778
- _loadStructure = _asyncToGenerator(function* (plugin, structureText, usePlddtColoring, showAxes) {
3779
- try {
3780
- var _plugin$canvas3d;
3781
- yield plugin.clear();
3782
- const data = yield plugin.builders.data.rawData({
3783
- data: structureText,
3784
- label: "Structure"
3785
- });
3786
- const trajectory = yield plugin.builders.structure.parseTrajectory(data, detectStructureFormat(structureText));
3787
- const model = yield plugin.builders.structure.createModel(trajectory);
3788
- const structure = yield plugin.builders.structure.createStructure(model, {
3789
- name: "model",
3790
- params: {}
3791
- });
3792
- const data3d = structure.data;
3793
- if (!data3d) return NOTHING_LOADED;
3794
- const { chainLabels, chains, residuesByChain } = scanChains(data3d);
3795
- const typeParams = representationTypeParams(plugin);
3796
- const componentRefs = /* @__PURE__ */ new Map();
3797
- for (const [chainId, label] of chainLabels) {
3798
- const refs = yield buildChainComponents(plugin, structure, {
3799
- chainId,
3800
- label
3801
- }, typeParams);
3802
- if (refs.length > 0) componentRefs.set(chainId, refs);
3803
- }
3804
- if (usePlddtColoring) yield applyColorTheme(plugin, PLDDT_THEME_NAME);
3805
- (_plugin$canvas3d = plugin.canvas3d) === null || _plugin$canvas3d === void 0 || _plugin$canvas3d.requestCameraReset();
3806
- setAxes(plugin, showAxes);
3807
- return {
3808
- chains,
3809
- componentRefs,
3810
- residuesByChain
3811
- };
3812
- } catch (error) {
3813
- console.error("Failed to load structure:", error);
3814
- return NOTHING_LOADED;
3815
- }
3816
- });
3817
- return _loadStructure.apply(this, arguments);
3818
- }
3819
- /**
3820
- * Shows or hides everything each chain is drawn with, cartoon and sticks
3821
- * alike. Every component under the chain, so hiding it takes its ligands with
3822
- * it rather than leaving a heme floating where its protein used to be.
3823
- *
3824
- * The whole subtree of each, not just the component cell: visibility is
3825
- * per-cell, and it is the representation underneath that actually draws.
3826
- *
3827
- * Set outright rather than through the component manager's `toggleVisibility`,
3828
- * which flips whatever it finds: the hidden set is derived from props on every
3829
- * pass, so it has to be applied as a statement of what should be true and not
3830
- * as a change to what already is.
3831
- */
3832
- function applyChainVisibility(plugin, componentRefs, hidden) {
3833
- for (const [chainId, refs] of componentRefs) for (const ref of refs) (0, molstar_lib_mol_plugin_behavior_static_state.setSubtreeVisibility)(plugin.state.data, ref, hidden.has(chainId));
3834
- }
3835
- /**
3836
- * Recolors every loaded structure's representations with the named theme,
3837
- * leaving the heteroatoms on `ELEMENT_THEME`.
3838
- *
3839
- * The per-representation form of `updateRepresentationsTheme`, so the two can
3840
- * be told apart in one pass; handing it a filtered list instead would leave
3841
- * the ligands on whichever theme happened to be current when they were built.
3842
- */
3843
- function applyColorTheme(_x13, _x14) {
3844
- return _applyColorTheme.apply(this, arguments);
3845
- }
3846
- function _applyColorTheme() {
3847
- _applyColorTheme = _asyncToGenerator(function* (plugin, colorTheme) {
3848
- yield plugin.dataTransaction(_asyncToGenerator(function* () {
3849
- for (const s of plugin.managers.structure.hierarchy.current.structures) yield plugin.managers.structure.component.updateRepresentationsTheme(s.components, (_component, representation) => ({ color: isLigandRepresentation(representation) ? ELEMENT_THEME : colorTheme }));
3850
- }));
3851
- });
3852
- return _applyColorTheme.apply(this, arguments);
3853
- }
3854
- /**
3855
2878
  * Owns the Mol* plugin for one viewer: creates it, loads the structure, wires
3856
2879
  * interaction callbacks, and disposes of it on unmount.
3857
2880
  *
3858
2881
  * The plugin is built once and then mutated in place. Creating it is expensive
3859
2882
  * and destroys the camera, so prop changes that Mol* can absorb (background,
3860
- * axes, coloring, new structure) are pushed in through the effects below and in
3861
- * the sibling hooks rather than by rebuilding.
2883
+ * axes, coloring, representation, new structure) are pushed in through the
2884
+ * effects below and in the sibling hooks rather than by rebuilding.
3862
2885
  */
3863
- function useMolstarPlugin({ backgroundColor, chainColors, containerRef, download, edgeColor, hasPlddt, hiddenChains, highlightColor, mode, molstarSpec, onChainHover, onChainSelect, onChainToggle, onResidueClick, onResidueHover, onSelectionChange, onSelectionClear, selectedChains, sequenceViewerBackgroundColor, showAxes, showSequenceViewer, structure }) {
2886
+ function useMolstarPlugin({ backgroundColor, containerRef, download, edgeColor, hiddenChains, highlightColor, mode, molstarSpec, onChainHover, onChainSelect, onChainToggle, onDispose, onError, onReady, onResidueClick, onResidueHover, onSelectionChange, onSelectionClear, onStructureLoad, sceneMode, sceneProps, selectedChains, sequenceViewerBackgroundColor, showAxes, showSequenceViewer, structure }) {
2887
+ var _sceneProps$chainColo;
3864
2888
  const pluginRef = (0, react.useRef)(null);
3865
- const residueValueThemeRef = (0, react.useRef)(null);
3866
- const chainColorThemeRef = (0, react.useRef)(null);
2889
+ const sceneRef = (0, react.useRef)(null);
3867
2890
  const currentStructureRef = (0, react.useRef)(null);
3868
2891
  const [isReady, setIsReady] = (0, react.useState)(false);
3869
2892
  const [chains, setChains] = (0, react.useState)([]);
3870
2893
  const [loadCount, setLoadCount] = (0, react.useState)(0);
3871
- /**
3872
- * Where each chain's components live in the state tree, which is what the
3873
- * visibility effect points `setSubtreeVisibility` at. Rewritten by every
3874
- * load, since the refs do not survive `plugin.clear()`.
3875
- */
3876
- const componentRefsRef = (0, react.useRef)(/* @__PURE__ */ new Map());
2894
+ const [sceneVersion, setSceneVersion] = (0, react.useState)(0);
2895
+ const [sceneBusy, setSceneBusy] = (0, react.useState)(false);
3877
2896
  /**
3878
2897
  * Which residues sit on each chain, for the readout to average a score over
3879
2898
  * a whole-chain selection. A ref rather than state: nothing renders from it
3880
2899
  * directly, and it is rewritten in step with `chains`, which does.
3881
2900
  */
3882
2901
  const residuesByChainRef = (0, react.useRef)(/* @__PURE__ */ new Map());
2902
+ const addressIndexRef = (0, react.useRef)(/* @__PURE__ */ new Map());
2903
+ const framedOrientationRef = (0, react.useRef)(void 0);
3883
2904
  /**
3884
- * The visibility and coloring to apply as soon as a structure exists. Read
3885
- * through refs because both arrive as fresh objects on every render, and a
3886
- * load has to be able to consult them without the load effect depending on
3887
- * them - a new Set each render would reload the structure on every pass.
2905
+ * The scene as the props describe it, read by a load when it comes to draw.
2906
+ * Through a ref because a load outlasts renders and has to draw what the
2907
+ * props say when it gets there, not what they said when it began.
3888
2908
  */
3889
- const hiddenChainsRef = (0, react.useRef)(hiddenChains);
3890
- hiddenChainsRef.current = hiddenChains;
3891
- const chainColorsRef = (0, react.useRef)(chainColors);
3892
- chainColorsRef.current = chainColors;
2909
+ const scenePropsRef = (0, react.useRef)(sceneProps);
2910
+ scenePropsRef.current = sceneProps;
2911
+ const initialPropsRef = (0, react.useRef)({
2912
+ backgroundColor,
2913
+ edgeColor,
2914
+ highlightColor,
2915
+ mode,
2916
+ molstarSpec,
2917
+ showAxes,
2918
+ showSequenceViewer,
2919
+ structure
2920
+ });
2921
+ initialPropsRef.current = {
2922
+ backgroundColor,
2923
+ edgeColor,
2924
+ highlightColor,
2925
+ mode,
2926
+ molstarSpec,
2927
+ showAxes,
2928
+ showSequenceViewer,
2929
+ structure
2930
+ };
3893
2931
  /**
3894
2932
  * Carries the theme-dependent props into the views Mol* renders in its own
3895
2933
  * React root. They cannot read them from context, and rebuilding the plugin
@@ -3911,19 +2949,17 @@ function useMolstarPlugin({ backgroundColor, chainColors, containerRef, download
3911
2949
  clipRatioRef.current = ratio;
3912
2950
  }, []);
3913
2951
  /**
3914
- * Takes up what a load produced: the component refs the chain-keyed props
3915
- * address, the visibility they were already asking for, and the chain list
3916
- * the consumer is told about.
2952
+ * Takes up what a load produced: what the chain-keyed props and addresses
2953
+ * are resolved against, and the chain list the consumer is told about.
3917
2954
  *
3918
2955
  * Chains are published only when they actually differ, so reloading the same
3919
- * structure - a theme switch that happens to reload, a parent handing back an
3920
- * equal PDB string - does not hand every consumer a new array to react to.
2956
+ * structure - a parent handing back an equal PDB string - does not hand every
2957
+ * consumer a new array to react to.
3921
2958
  */
3922
- const adoptLoadedStructure = (0, react.useCallback)((plugin, loaded) => {
3923
- componentRefsRef.current = loaded.componentRefs;
2959
+ const adoptLoadedStructure = (0, react.useCallback)((loaded) => {
3924
2960
  residuesByChainRef.current = loaded.residuesByChain;
3925
- applyChainVisibility(plugin, loaded.componentRefs, hiddenChainsRef.current);
3926
- setChains((prev) => chainsEqual(prev, loaded.chains) ? prev : loaded.chains);
2961
+ addressIndexRef.current = loaded.addressIndex;
2962
+ setChains((prev) => require_renderStructureImage.chainsEqual(prev, loaded.chains) ? prev : loaded.chains);
3927
2963
  setLoadCount((count) => count + 1);
3928
2964
  }, []);
3929
2965
  const onResidueClickRef = (0, react.useRef)(onResidueClick);
@@ -3934,30 +2970,113 @@ function useMolstarPlugin({ backgroundColor, chainColors, containerRef, download
3934
2970
  onSelectionChangeRef.current = onSelectionChange;
3935
2971
  const onResidueHoverRef = (0, react.useRef)(onResidueHover);
3936
2972
  onResidueHoverRef.current = onResidueHover;
2973
+ const onReadyRef = (0, react.useRef)(onReady);
2974
+ onReadyRef.current = onReady;
2975
+ const onErrorRef = (0, react.useRef)(onError);
2976
+ onErrorRef.current = onError;
2977
+ const onDisposeRef = (0, react.useRef)(onDispose);
2978
+ onDisposeRef.current = onDispose;
2979
+ const onStructureLoadRef = (0, react.useRef)(onStructureLoad);
2980
+ onStructureLoadRef.current = onStructureLoad;
2981
+ /**
2982
+ * Fixed at the first render: the load path draws or declines to draw by it,
2983
+ * so a mode that changed under a live plugin would leave its scene half one
2984
+ * and half the other.
2985
+ */
2986
+ const sceneModeRef = (0, react.useRef)(sceneMode);
2987
+ /**
2988
+ * The plugin last handed to `onReady`, so that `onDispose` is only called for
2989
+ * one a consumer has actually been given - and is called for it even while
2990
+ * its `onReady` is still running.
2991
+ */
2992
+ const handedOverRef = (0, react.useRef)(null);
3937
2993
  /** Last residue reported to `onResidueHover`, to suppress repeats. */
3938
2994
  const lastHoverRef = (0, react.useRef)(null);
3939
- const initialPropsRef = (0, react.useRef)({
3940
- backgroundColor,
3941
- edgeColor,
3942
- hasPlddt,
3943
- highlightColor,
3944
- mode,
3945
- molstarSpec,
3946
- showAxes,
3947
- showSequenceViewer,
3948
- structure
3949
- });
3950
- initialPropsRef.current = {
3951
- backgroundColor,
3952
- edgeColor,
3953
- hasPlddt,
3954
- highlightColor,
3955
- mode,
3956
- molstarSpec,
3957
- showAxes,
3958
- showSequenceViewer,
3959
- structure
3960
- };
2995
+ const reportError = (0, react.useCallback)((error, phase) => {
2996
+ const handler = onErrorRef.current;
2997
+ if (handler) handler(error, phase);
2998
+ else console.error(UNHANDLED_ERROR_MESSAGES[phase], error);
2999
+ }, []);
3000
+ /**
3001
+ * Hands a loaded structure to `onReady` and waits for it.
3002
+ *
3003
+ * `isCurrent` says whether the plugin is still the viewer's own by the time
3004
+ * the consumer is done with it. A failure after that is not reported: the
3005
+ * plugin it happened on is gone, and the consumer has been told so through
3006
+ * `onDispose` already.
3007
+ */
3008
+ const handOver = (0, react.useCallback)(function() {
3009
+ var _ref = require_renderStructureImage._asyncToGenerator(function* (plugin, loaded, isCurrent) {
3010
+ const ready = onReadyRef.current;
3011
+ const { atomCount, chains: loadedChains, structure: parsed } = loaded;
3012
+ if (!ready || !parsed) return;
3013
+ handedOverRef.current = plugin;
3014
+ try {
3015
+ yield ready(plugin, {
3016
+ atomCount,
3017
+ chains: loadedChains,
3018
+ structure: parsed
3019
+ });
3020
+ } catch (error) {
3021
+ if (isCurrent()) reportError(error, "load");
3022
+ }
3023
+ });
3024
+ return function(_x2, _x3, _x4) {
3025
+ return _ref.apply(this, arguments);
3026
+ };
3027
+ }(), [reportError]);
3028
+ /**
3029
+ * Takes up whatever a load produced. A failed load leaves the viewer holding
3030
+ * nothing, as a structure with no chains would, and is reported rather than
3031
+ * handed to `onReady`.
3032
+ */
3033
+ const takeUpLoad = (0, react.useCallback)(function() {
3034
+ var _ref2 = require_renderStructureImage._asyncToGenerator(function* (plugin, outcome, isCurrent) {
3035
+ var _onStructureLoadRef$c;
3036
+ if (!isCurrent()) return;
3037
+ if (!outcome.ok) {
3038
+ adoptLoadedStructure(require_renderStructureImage.NOTHING_LOADED);
3039
+ reportError(outcome.error, "load");
3040
+ return;
3041
+ }
3042
+ const { loaded } = outcome;
3043
+ adoptLoadedStructure(loaded);
3044
+ (_onStructureLoadRef$c = onStructureLoadRef.current) === null || _onStructureLoadRef$c === void 0 || _onStructureLoadRef$c.call(onStructureLoadRef, require_renderStructureImage.loadInfo(loaded));
3045
+ yield handOver(plugin, loaded, isCurrent);
3046
+ });
3047
+ return function(_x5, _x6, _x7) {
3048
+ return _ref2.apply(this, arguments);
3049
+ };
3050
+ }(), [
3051
+ adoptLoadedStructure,
3052
+ handOver,
3053
+ reportError
3054
+ ]);
3055
+ /**
3056
+ * Loads a structure and brings everything up on it: parses it, draws the
3057
+ * scene when the viewer is the one drawing it, places the camera, and hands
3058
+ * it over. Runs inside the scene's queue, so it never interleaves with an
3059
+ * update to the structure it is replacing.
3060
+ *
3061
+ * The camera is placed once something is drawn: Mol* settles a reset on the
3062
+ * next frame, and one settled against an empty scene is lost. So it follows
3063
+ * the viewer's own scene when there is one, and the consumer's `onReady`
3064
+ * when the consumer draws.
3065
+ */
3066
+ const loadAndDraw = (0, react.useCallback)(function() {
3067
+ var _ref3 = require_renderStructureImage._asyncToGenerator(function* (plugin, scene, text, isCurrent) {
3068
+ const outcome = yield scene.load(text, scenePropsRef.current);
3069
+ if (!isCurrent()) return;
3070
+ const managed = sceneModeRef.current === "managed";
3071
+ if (outcome.ok) setAxes(plugin, initialPropsRef.current.showAxes);
3072
+ if (managed && outcome.ok) framedOrientationRef.current = scene.frame(scenePropsRef.current, true);
3073
+ yield takeUpLoad(plugin, outcome, isCurrent);
3074
+ if (!managed && outcome.ok && isCurrent()) framedOrientationRef.current = scene.frame(scenePropsRef.current, false);
3075
+ });
3076
+ return function(_x8, _x9, _x10, _x11) {
3077
+ return _ref3.apply(this, arguments);
3078
+ };
3079
+ }(), [takeUpLoad]);
3961
3080
  /**
3962
3081
  * The consumer's `canvas3d` overrides, keyed by content. Unlike the rest of
3963
3082
  * the spec these are re-applied when they change, and they commonly arrive
@@ -3978,7 +3097,7 @@ function useMolstarPlugin({ backgroundColor, chainColors, containerRef, download
3978
3097
  let cancelled = false;
3979
3098
  let clipSubscription;
3980
3099
  const init = function() {
3981
- var _ref2 = _asyncToGenerator(function* () {
3100
+ var _ref4 = require_renderStructureImage._asyncToGenerator(function* () {
3982
3101
  yield nextFrame();
3983
3102
  if (cancelled) return;
3984
3103
  const rect = container.getBoundingClientRect();
@@ -3988,16 +3107,12 @@ function useMolstarPlugin({ backgroundColor, chainColors, containerRef, download
3988
3107
  }
3989
3108
  const initial = initialPropsRef.current;
3990
3109
  try {
3991
- var _plugin$canvas3d2;
3992
- const residueValueTheme = createResidueValueTheme(initial.mode);
3993
- const chainColorTheme = createChainColorTheme();
3110
+ var _plugin$canvas3d;
3994
3111
  const plugin = yield createViewer({
3995
3112
  backgroundColor: initial.backgroundColor,
3996
- chainColorTheme,
3997
3113
  edgeColor: initial.edgeColor,
3998
3114
  highlightColor: initial.highlightColor,
3999
3115
  molstarSpec: initial.molstarSpec,
4000
- residueValueTheme,
4001
3116
  root: container,
4002
3117
  showAxes: initial.showAxes,
4003
3118
  showSequenceViewer: initial.showSequenceViewer,
@@ -4008,12 +3123,18 @@ function useMolstarPlugin({ backgroundColor, chainColors, containerRef, download
4008
3123
  return;
4009
3124
  }
4010
3125
  pluginRef.current = plugin;
4011
- residueValueThemeRef.current = residueValueTheme;
4012
- chainColorThemeRef.current = chainColorTheme;
3126
+ const scene = new require_renderStructureImage.StructureScene(plugin, {
3127
+ draw: sceneModeRef.current === "managed",
3128
+ mode: initial.mode,
3129
+ onBusyChange: setSceneBusy,
3130
+ onRebuild: () => setSceneVersion((version) => version + 1),
3131
+ reportError
3132
+ });
3133
+ sceneRef.current = scene;
4013
3134
  const latest = initialPropsRef.current;
4014
- const loaded = yield loadStructure(plugin, latest.structure, latest.hasPlddt, latest.showAxes);
4015
3135
  currentStructureRef.current = latest.structure;
4016
- adoptLoadedStructure(plugin, loaded);
3136
+ yield scene.enqueue(() => loadAndDraw(plugin, scene, latest.structure, () => !cancelled));
3137
+ if (cancelled) return;
4017
3138
  /**
4018
3139
  * Mol*'s click behavior is a BehaviorSubject, so subscribing replays
4019
3140
  * whatever it is currently holding - which, before anything has been
@@ -4051,37 +3172,46 @@ function useMolstarPlugin({ backgroundColor, chainColors, containerRef, download
4051
3172
  lastHoverRef.current = residue;
4052
3173
  (_onResidueHoverRef$cu = onResidueHoverRef.current) === null || _onResidueHoverRef$cu === void 0 || _onResidueHoverRef$cu.call(onResidueHoverRef, residue);
4053
3174
  });
4054
- clipSubscription = (_plugin$canvas3d2 = plugin.canvas3d) === null || _plugin$canvas3d2 === void 0 ? void 0 : _plugin$canvas3d2.didDraw.subscribe(() => {
3175
+ clipSubscription = (_plugin$canvas3d = plugin.canvas3d) === null || _plugin$canvas3d === void 0 ? void 0 : _plugin$canvas3d.didDraw.subscribe(() => {
4055
3176
  syncClipToZoom(plugin, () => clipRatioRef.current);
4056
3177
  });
4057
3178
  if (!cancelled) setIsReady(true);
4058
3179
  } catch (error) {
4059
- console.error("Failed to initialize Mol* viewer:", error);
3180
+ if (!cancelled) reportError(error, "init");
4060
3181
  }
4061
3182
  });
4062
3183
  return function init() {
4063
- return _ref2.apply(this, arguments);
3184
+ return _ref4.apply(this, arguments);
4064
3185
  };
4065
3186
  }();
4066
3187
  init();
4067
3188
  return () => {
4068
- var _pluginRef$current;
3189
+ var _sceneRef$current;
4069
3190
  cancelled = true;
4070
3191
  clipSubscription === null || clipSubscription === void 0 || clipSubscription.unsubscribe();
4071
- (_pluginRef$current = pluginRef.current) === null || _pluginRef$current === void 0 || _pluginRef$current.dispose();
3192
+ (_sceneRef$current = sceneRef.current) === null || _sceneRef$current === void 0 || _sceneRef$current.dispose();
3193
+ sceneRef.current = null;
3194
+ const plugin = pluginRef.current;
3195
+ try {
3196
+ if (plugin && handedOverRef.current === plugin) {
3197
+ var _onDisposeRef$current;
3198
+ handedOverRef.current = null;
3199
+ (_onDisposeRef$current = onDisposeRef.current) === null || _onDisposeRef$current === void 0 || _onDisposeRef$current.call(onDisposeRef);
3200
+ }
3201
+ } finally {
3202
+ plugin === null || plugin === void 0 || plugin.dispose();
3203
+ }
4072
3204
  pluginRef.current = null;
4073
- residueValueThemeRef.current = null;
4074
- chainColorThemeRef.current = null;
4075
- componentRefsRef.current = /* @__PURE__ */ new Map();
4076
3205
  residuesByChainRef.current = /* @__PURE__ */ new Map();
3206
+ addressIndexRef.current = /* @__PURE__ */ new Map();
4077
3207
  currentStructureRef.current = null;
4078
3208
  clipRatioRef.current = null;
4079
3209
  setIsReady(false);
4080
3210
  };
4081
3211
  }, []);
4082
3212
  (0, react.useEffect)(() => {
4083
- var _pluginRef$current2, _initialPropsRef$curr;
4084
- const canvas3d = (_pluginRef$current2 = pluginRef.current) === null || _pluginRef$current2 === void 0 ? void 0 : _pluginRef$current2.canvas3d;
3213
+ var _pluginRef$current, _initialPropsRef$curr;
3214
+ const canvas3d = (_pluginRef$current = pluginRef.current) === null || _pluginRef$current === void 0 ? void 0 : _pluginRef$current.canvas3d;
4085
3215
  if (!canvas3d || !isReady) return;
4086
3216
  canvas3d.setProps({
4087
3217
  marking: {
@@ -4110,6 +3240,8 @@ function useMolstarPlugin({ backgroundColor, chainColors, containerRef, download
4110
3240
  onChainHover,
4111
3241
  onChainSelect,
4112
3242
  onChainToggle,
3243
+ onError: reportError,
3244
+ sceneBusy,
4113
3245
  selectedChains,
4114
3246
  sequenceViewerBackgroundColor,
4115
3247
  showAxes
@@ -4121,6 +3253,8 @@ function useMolstarPlugin({ backgroundColor, chainColors, containerRef, download
4121
3253
  onChainHover,
4122
3254
  onChainSelect,
4123
3255
  onChainToggle,
3256
+ reportError,
3257
+ sceneBusy,
4124
3258
  selectedChains,
4125
3259
  sequenceViewerBackgroundColor,
4126
3260
  showAxes,
@@ -4132,34 +3266,55 @@ function useMolstarPlugin({ backgroundColor, chainColors, containerRef, download
4132
3266
  }, [showAxes, isReady]);
4133
3267
  (0, react.useEffect)(() => {
4134
3268
  const plugin = pluginRef.current;
4135
- if (!plugin || !isReady) return;
3269
+ const scene = sceneRef.current;
3270
+ if (!plugin || !scene || !isReady) return;
4136
3271
  if (structure === currentStructureRef.current) return;
4137
3272
  currentStructureRef.current = structure;
4138
3273
  clipRatioRef.current = null;
4139
3274
  lastHoverRef.current = null;
4140
- loadStructure(plugin, structure, hasPlddt, showAxes).then((loaded) => {
4141
- if (pluginRef.current !== plugin) return;
4142
- adoptLoadedStructure(plugin, loaded);
3275
+ scene.enqueue(() => loadAndDraw(plugin, scene, structure, () => pluginRef.current === plugin)).catch((error) => {
3276
+ if (pluginRef.current === plugin) reportError(error, "load");
4143
3277
  });
4144
3278
  }, [
4145
3279
  structure,
4146
- hasPlddt,
4147
3280
  isReady,
4148
- adoptLoadedStructure
3281
+ loadAndDraw,
3282
+ reportError
4149
3283
  ]);
3284
+ /**
3285
+ * Keys for the scene props that arrive as fresh objects on every render, so
3286
+ * the update below turns on what they say rather than on their identity.
3287
+ * `plddt` and the overlay are compared by identity, as they are elsewhere.
3288
+ */
3289
+ const hiddenKey = require_renderStructureImage.chainSetKey(sceneProps.hiddenChains);
3290
+ const sceneHighlightsKey = require_renderStructureImage.highlightsKey(sceneProps.highlights);
3291
+ const chainColorsKey = JSON.stringify((_sceneProps$chainColo = sceneProps.chainColors) !== null && _sceneProps$chainColo !== void 0 ? _sceneProps$chainColo : null);
4150
3292
  (0, react.useEffect)(() => {
4151
- const plugin = pluginRef.current;
4152
- if (!plugin || !isReady) return;
4153
- applyChainVisibility(plugin, componentRefsRef.current, hiddenChains);
4154
- }, [hiddenChains, isReady]);
3293
+ const scene = sceneRef.current;
3294
+ if (!scene || !isReady || sceneModeRef.current !== "managed") return;
3295
+ scene.update(scenePropsRef.current);
3296
+ }, [
3297
+ isReady,
3298
+ sceneProps.representation,
3299
+ hiddenKey,
3300
+ sceneHighlightsKey,
3301
+ sceneProps.colorBy,
3302
+ chainColorsKey,
3303
+ sceneProps.plddt,
3304
+ sceneProps.overlay,
3305
+ sceneProps.mode
3306
+ ]);
4155
3307
  return {
4156
- chainColorThemeRef,
3308
+ addressIndexRef,
4157
3309
  chains,
3310
+ framedOrientationRef,
4158
3311
  isReady,
4159
3312
  loadCount,
4160
3313
  pluginRef,
4161
3314
  residuesByChainRef,
4162
- residueValueThemeRef,
3315
+ sceneMode: sceneModeRef.current,
3316
+ sceneRef,
3317
+ sceneVersion,
4163
3318
  setClipRatio
4164
3319
  };
4165
3320
  }
@@ -4232,7 +3387,7 @@ function setFocusNeighbourhood(_x, _x2) {
4232
3387
  return _setFocusNeighbourhood.apply(this, arguments);
4233
3388
  }
4234
3389
  function _setFocusNeighbourhood() {
4235
- _setFocusNeighbourhood = _asyncToGenerator(function* (plugin, includeNeighbours) {
3390
+ _setFocusNeighbourhood = require_renderStructureImage._asyncToGenerator(function* (plugin, includeNeighbours) {
4236
3391
  const expandRadius = includeNeighbours ? NEIGHBOUR_RADIUS : OWN_RESIDUES_ONLY;
4237
3392
  yield plugin.state.updateBehavior(molstar_lib_mol_plugin_behavior_dynamic_selection_structure_focus_representation.StructureFocusRepresentation, (params) => {
4238
3393
  params.components = FOCUS_COMPONENTS;
@@ -4342,7 +3497,7 @@ function useSelectionFocus({ hiddenChains, isReady, pluginRef, selection, setCli
4342
3497
  * itself - and comparing strings rather than objects is what keeps a consumer
4343
3498
  * echoing an equal selection back from refocusing on every render.
4344
3499
  */
4345
- const focusKey = selection ? `${selectionKey(selection)}#${[...hiddenChains].sort().join(",")}` : null;
3500
+ const focusKey = selection ? `${require_renderStructureImage.selectionKey(selection)}#${[...hiddenChains].sort().join(",")}` : null;
4346
3501
  /**
4347
3502
  * Whether the selection names chains and nothing else, which is the shape
4348
3503
  * shown by dimming the chains around it rather than by focusing it.
@@ -4363,7 +3518,7 @@ function useSelectionFocus({ hiddenChains, isReady, pluginRef, selection, setCli
4363
3518
  return;
4364
3519
  }
4365
3520
  const chosen = selection;
4366
- const loci = lociForSelection(plugin, chosen, hiddenChains);
3521
+ const loci = require_renderStructureImage.lociForSelection(plugin, chosen, hiddenChains);
4367
3522
  if (!loci) {
4368
3523
  clearFocus(plugin, setClipRatio, false);
4369
3524
  return;
@@ -4431,34 +3586,34 @@ function useSelectionReadout({ hoveredResidue, isReady, plddt, pluginRef, residu
4431
3586
  (0, react.useEffect)(() => {
4432
3587
  var _residuesByChainRef$c;
4433
3588
  const plugin = pluginRef.current;
4434
- if (!plugin || !isReady || isEmptySelection(selection)) {
3589
+ if (!plugin || !isReady || require_renderStructureImage.isEmptySelection(selection)) {
4435
3590
  setResolved(null);
4436
3591
  return;
4437
3592
  }
4438
3593
  const chosen = selection;
4439
- const residues = selectionResidues(chosen, (_residuesByChainRef$c = residuesByChainRef.current) !== null && _residuesByChainRef$c !== void 0 ? _residuesByChainRef$c : /* @__PURE__ */ new Map());
3594
+ const residues = require_renderStructureImage.selectionResidues(chosen, (_residuesByChainRef$c = residuesByChainRef.current) !== null && _residuesByChainRef$c !== void 0 ? _residuesByChainRef$c : /* @__PURE__ */ new Map());
4440
3595
  if (residues.length === 0) {
4441
3596
  setResolved(null);
4442
3597
  return;
4443
3598
  }
4444
3599
  if (residues.length > 1) {
4445
3600
  setResolved({
4446
- label: selectionLabel(chosen, residues.length),
3601
+ label: require_renderStructureImage.selectionLabel(chosen, residues.length),
4447
3602
  residues
4448
3603
  });
4449
3604
  return;
4450
3605
  }
4451
- const loci = lociForSelection(plugin, chosen);
3606
+ const loci = require_renderStructureImage.lociForSelection(plugin, chosen);
4452
3607
  const residue = loci && residueRefFromLoci(loci);
4453
3608
  setResolved(residue ? {
4454
3609
  label: residueLabel(residue),
4455
3610
  residues
4456
3611
  } : null);
4457
- }, [isReady, selection && selectionKey(selection)]);
3612
+ }, [isReady, selection && require_renderStructureImage.selectionKey(selection)]);
4458
3613
  const buildReadout = (0, react.useCallback)((label, residues) => ({
4459
3614
  label,
4460
- plddt: meanOverResidues(residues, (i) => plddt === null || plddt === void 0 ? void 0 : plddt[i]),
4461
- value: meanOverResidues(residues, (i) => residueOverlay === null || residueOverlay === void 0 ? void 0 : residueOverlay.values.get(i)),
3615
+ plddt: require_renderStructureImage.meanOverResidues(residues, (i) => plddt === null || plddt === void 0 ? void 0 : plddt[i]),
3616
+ value: require_renderStructureImage.meanOverResidues(residues, (i) => residueOverlay === null || residueOverlay === void 0 ? void 0 : residueOverlay.values.get(i)),
4462
3617
  residueCount: residues.length
4463
3618
  }), [plddt, residueOverlay]);
4464
3619
  return {
@@ -4467,52 +3622,6 @@ function useSelectionReadout({ hoveredResidue, isReady, plddt, pluginRef, residu
4467
3622
  };
4468
3623
  }
4469
3624
  //#endregion
4470
- //#region src/core/ProteinStructureViewer/hooks/useStructureColoring.ts
4471
- /**
4472
- * Decides what is coloring the structure and keeps that theme's data current.
4473
- *
4474
- * Three themes compete for the same representations, in a fixed order: a
4475
- * residue value overlay, then pLDDT scores, then per-chain colors. Only one
4476
- * can be applied, so the precedence lives here rather than being spread across
4477
- * the hooks that own each theme's data - and the legend resolves what to
4478
- * describe the same way, so the two cannot disagree about what is on screen.
4479
- *
4480
- * Recoloring happens in place: the camera is left where it is, so selecting or
4481
- * switching an overlay does not zoom back out to the default view.
4482
- */
4483
- function useStructureColoring({ chainColorThemeRef, chainColors, hasPlddt, isReady, mode, overlay, pluginRef, residueValueThemeRef }) {
4484
- (0, react.useEffect)(() => {
4485
- const plugin = pluginRef.current;
4486
- const residueTheme = residueValueThemeRef.current;
4487
- const chainTheme = chainColorThemeRef.current;
4488
- if (!plugin || !residueTheme || !chainTheme || !isReady) return;
4489
- chainTheme.setState(chainColors);
4490
- if (overlay) {
4491
- var _overlay$colorScale, _overlay$min;
4492
- residueTheme.setState({
4493
- colorScale: (_overlay$colorScale = overlay.colorScale) !== null && _overlay$colorScale !== void 0 ? _overlay$colorScale : require_colorScales.PLASMA_COLOR_SCALE,
4494
- max: overlay.max,
4495
- min: (_overlay$min = overlay.min) !== null && _overlay$min !== void 0 ? _overlay$min : 0,
4496
- mode,
4497
- values: overlay.values
4498
- });
4499
- applyColorTheme(plugin, RESIDUE_VALUE_THEME_NAME);
4500
- return;
4501
- }
4502
- residueTheme.setState({
4503
- mode,
4504
- values: /* @__PURE__ */ new Map()
4505
- });
4506
- applyColorTheme(plugin, hasPlddt ? PLDDT_THEME_NAME : FALLBACK_THEME_NAME);
4507
- }, [
4508
- overlay,
4509
- hasPlddt,
4510
- isReady,
4511
- mode,
4512
- chainColors
4513
- ]);
4514
- }
4515
- //#endregion
4516
3625
  //#region src/core/ProteinStructureViewer/ProteinStructureViewer.types.ts
4517
3626
  /** Pixel dimensions each resolution produces, for documentation and labels. */
4518
3627
  const DOWNLOAD_RESOLUTIONS = {
@@ -4526,20 +3635,33 @@ const DOWNLOAD_RESOLUTIONS = {
4526
3635
  const _excluded = [
4527
3636
  "backgroundColor",
4528
3637
  "chainColors",
3638
+ "colorBy",
4529
3639
  "disableChainHighlightOnHover",
4530
3640
  "download",
4531
3641
  "hiddenChains",
3642
+ "highlights",
3643
+ "initialCamera",
3644
+ "onCameraChange",
4532
3645
  "onChainVisibilityChange",
4533
3646
  "onChainsChange",
4534
3647
  "molstarSpec",
3648
+ "onDispose",
3649
+ "onError",
3650
+ "onReady",
4535
3651
  "onResidueClick",
4536
3652
  "onResidueHover",
4537
3653
  "onSelectionChange",
3654
+ "onStructureLoad",
3655
+ "orientation",
3656
+ "projection",
3657
+ "representation",
4538
3658
  "structure",
4539
3659
  "plddt",
4540
3660
  "residueOverlay",
3661
+ "sceneMode",
4541
3662
  "selection",
4542
3663
  "sequenceViewerBackgroundColor",
3664
+ "sequenceViewerHeight",
4543
3665
  "showAxes",
4544
3666
  "showChainLegend",
4545
3667
  "showLegend",
@@ -4564,16 +3686,26 @@ const PLDDT_SCALE_LABEL = "pLDDT";
4564
3686
  const DEFAULT_OVERLAY_LABEL = "Value";
4565
3687
  /** Readout slot label used when an overlay does not name one. */
4566
3688
  const DEFAULT_READOUT_LABEL = "Value";
3689
+ const NO_SCALE = {
3690
+ scale: null,
3691
+ scaleLabel: void 0,
3692
+ scaleMax: null,
3693
+ scaleTooltip: void 0,
3694
+ scaleTooltipProps: void 0,
3695
+ valueLabel: void 0
3696
+ };
4567
3697
  /**
4568
3698
  * Chooses what the legend describes, following whatever is actually coloring
4569
- * the structure: the overlay when one is set, the pLDDT bands when scores were
4570
- * supplied, and nothing at all otherwise - Mol* falls back to chain coloring
4571
- * there, which no per-residue scale describes, so a key would be labelling
4572
- * colors that are not on screen.
4573
- */
4574
- function resolveScaleProps(overlay, hasPlddt) {
4575
- if (overlay) {
3699
+ * the structure: the overlay when it is painting, the pLDDT bands when they
3700
+ * are, and nothing at all for chain coloring - which no per-residue scale
3701
+ * describes, so a key would be labelling colors that are not on screen. An
3702
+ * overlay named by `colorBy` but not supplied paints everything neutral, and
3703
+ * has no key either.
3704
+ */
3705
+ function resolveScaleProps(colorBy, overlay) {
3706
+ if (colorBy === "overlay") {
4576
3707
  var _overlay$colorScale, _overlay$label, _overlay$min, _overlay$readoutLabel;
3708
+ if (!overlay) return NO_SCALE;
4577
3709
  return {
4578
3710
  scale: (_overlay$colorScale = overlay.colorScale) !== null && _overlay$colorScale !== void 0 ? _overlay$colorScale : require_colorScales.PLASMA_COLOR_SCALE,
4579
3711
  scaleLabel: (_overlay$label = overlay.label) !== null && _overlay$label !== void 0 ? _overlay$label : DEFAULT_OVERLAY_LABEL,
@@ -4584,22 +3716,11 @@ function resolveScaleProps(overlay, hasPlddt) {
4584
3716
  valueLabel: (_overlay$readoutLabel = overlay.readoutLabel) !== null && _overlay$readoutLabel !== void 0 ? _overlay$readoutLabel : DEFAULT_READOUT_LABEL
4585
3717
  };
4586
3718
  }
4587
- if (hasPlddt) return {
4588
- scale: PLDDT_COLOR_SCALE,
4589
- scaleLabel: PLDDT_SCALE_LABEL,
4590
- scaleMax: null,
4591
- scaleTooltip: void 0,
4592
- scaleTooltipProps: void 0,
4593
- valueLabel: void 0
4594
- };
4595
- return {
4596
- scale: null,
4597
- scaleLabel: void 0,
4598
- scaleMax: null,
4599
- scaleTooltip: void 0,
4600
- scaleTooltipProps: void 0,
4601
- valueLabel: void 0
4602
- };
3719
+ if (colorBy === "plddt") return require_objectSpread2._objectSpread2(require_objectSpread2._objectSpread2({}, NO_SCALE), {}, {
3720
+ scale: require_renderStructureImage.PLDDT_COLOR_SCALE,
3721
+ scaleLabel: PLDDT_SCALE_LABEL
3722
+ });
3723
+ return NO_SCALE;
4603
3724
  }
4604
3725
  /**
4605
3726
  * Interactive 3D protein structure viewer built on Mol*, with a sequence panel,
@@ -4614,20 +3735,15 @@ function resolveScaleProps(overlay, hasPlddt) {
4614
3735
  */
4615
3736
  const ProteinStructureViewer = (0, react.forwardRef)((props, ref) => {
4616
3737
  var _selection$chains;
4617
- const { backgroundColor, chainColors: chainColorOverrides, disableChainHighlightOnHover = false, download, hiddenChains: hiddenChainsProp, onChainVisibilityChange, onChainsChange, molstarSpec, onResidueClick, onResidueHover, onSelectionChange, structure, plddt, residueOverlay, selection: selectionProp, sequenceViewerBackgroundColor, showAxes = true, showChainLegend = true, showLegend = true, showSequenceViewer = true, stats } = props, rest = require_objectSpread2._objectWithoutProperties(props, _excluded);
3738
+ const { backgroundColor, chainColors: chainColorOverrides, colorBy: colorByProp, disableChainHighlightOnHover = false, download, hiddenChains: hiddenChainsProp, highlights, initialCamera, onCameraChange, onChainVisibilityChange, onChainsChange, molstarSpec, onDispose, onError, onReady, onResidueClick, onResidueHover, onSelectionChange, onStructureLoad, orientation, projection, representation = "cartoon", structure, plddt, residueOverlay, sceneMode = "managed", selection: selectionProp, sequenceViewerBackgroundColor, sequenceViewerHeight, showAxes = true, showChainLegend = true, showLegend = true, showSequenceViewer = true, stats } = props, rest = require_objectSpread2._objectWithoutProperties(props, _excluded);
4618
3739
  const theme = (0, _mui_material_styles.useTheme)();
4619
3740
  const mode = (0, _czi_sds_components.getMode)({ theme });
4620
3741
  const semanticColors = (0, _czi_sds_components.getSemanticColors)({ theme });
4621
3742
  const pluginMountRef = (0, react.useRef)(null);
4622
- const hasPlddt = Boolean(plddt && plddt.length > 0);
4623
- const processedStructure = (0, react.useMemo)(() => hasPlddt ? injectPlddt(structure, plddt) : structure, [
4624
- structure,
4625
- plddt,
4626
- hasPlddt
4627
- ]);
3743
+ const colorBy = require_renderStructureImage.resolveColorBy(colorByProp, residueOverlay, plddt);
4628
3744
  const bgColor = (0, react.useMemo)(() => {
4629
3745
  var _semanticColors$base;
4630
- return themeColor(backgroundColor !== null && backgroundColor !== void 0 ? backgroundColor : semanticColors === null || semanticColors === void 0 || (_semanticColors$base = semanticColors.base) === null || _semanticColors$base === void 0 ? void 0 : _semanticColors$base.backgroundPrimary, FALLBACK_BACKGROUND_COLOR[mode]);
3746
+ return require_renderStructureImage.themeColor(backgroundColor !== null && backgroundColor !== void 0 ? backgroundColor : semanticColors === null || semanticColors === void 0 || (_semanticColors$base = semanticColors.base) === null || _semanticColors$base === void 0 ? void 0 : _semanticColors$base.backgroundPrimary, FALLBACK_BACKGROUND_COLOR[mode]);
4631
3747
  }, [
4632
3748
  backgroundColor,
4633
3749
  mode,
@@ -4635,11 +3751,11 @@ const ProteinStructureViewer = (0, react.forwardRef)((props, ref) => {
4635
3751
  ]);
4636
3752
  const edgeColor = (0, react.useMemo)(() => {
4637
3753
  var _semanticColors$base2;
4638
- return themeColor(semanticColors === null || semanticColors === void 0 || (_semanticColors$base2 = semanticColors.base) === null || _semanticColors$base2 === void 0 ? void 0 : _semanticColors$base2.borderPrimary, FALLBACK_EDGE_COLOR);
3754
+ return require_renderStructureImage.themeColor(semanticColors === null || semanticColors === void 0 || (_semanticColors$base2 = semanticColors.base) === null || _semanticColors$base2 === void 0 ? void 0 : _semanticColors$base2.borderPrimary, FALLBACK_EDGE_COLOR);
4639
3755
  }, [semanticColors]);
4640
3756
  const highlightColor = (0, react.useMemo)(() => {
4641
3757
  var _semanticColors$base3;
4642
- return themeColor(semanticColors === null || semanticColors === void 0 || (_semanticColors$base3 = semanticColors.base) === null || _semanticColors$base3 === void 0 ? void 0 : _semanticColors$base3.fillSelected, FALLBACK_HIGHLIGHT_COLOR);
3758
+ return require_renderStructureImage.themeColor(semanticColors === null || semanticColors === void 0 || (_semanticColors$base3 = semanticColors.base) === null || _semanticColors$base3 === void 0 ? void 0 : _semanticColors$base3.fillSelected, FALLBACK_HIGHLIGHT_COLOR);
4643
3759
  }, [semanticColors]);
4644
3760
  const { handleResidueHover, hoveredResidue } = useResidueHoverState(onResidueHover);
4645
3761
  const { changeSelection, selection } = useSelection({
@@ -4668,8 +3784,8 @@ const ProteinStructureViewer = (0, react.forwardRef)((props, ref) => {
4668
3784
  * than undoing itself.
4669
3785
  */
4670
3786
  const handleChainSelect = (0, react.useCallback)((chainId) => {
4671
- var _selection$chains2, _selection$residues;
4672
- changeSelection((selection === null || selection === void 0 || (_selection$chains2 = selection.chains) === null || _selection$chains2 === void 0 ? void 0 : _selection$chains2.length) === 1 && selection.chains[0] === chainId && !((_selection$residues = selection.residues) === null || _selection$residues === void 0 ? void 0 : _selection$residues.length) ? null : { chains: [chainId] });
3787
+ var _selection$chains2, _selection$residues, _selection$addresses;
3788
+ changeSelection((selection === null || selection === void 0 || (_selection$chains2 = selection.chains) === null || _selection$chains2 === void 0 ? void 0 : _selection$chains2.length) === 1 && selection.chains[0] === chainId && !((_selection$residues = selection.residues) === null || _selection$residues === void 0 ? void 0 : _selection$residues.length) && !((_selection$addresses = selection.addresses) === null || _selection$addresses === void 0 ? void 0 : _selection$addresses.length) ? null : { chains: [chainId] });
4673
3789
  }, [changeSelection, selection]);
4674
3790
  /**
4675
3791
  * Dims every other chain while a chain's name is pointed at, in the legend
@@ -4693,13 +3809,11 @@ const ProteinStructureViewer = (0, react.forwardRef)((props, ref) => {
4693
3809
  hiddenChains: hiddenChainsProp,
4694
3810
  onChainVisibilityChange
4695
3811
  });
4696
- const { chainColorThemeRef, chains: loadedChains, isReady, loadCount, pluginRef, residuesByChainRef, residueValueThemeRef, setClipRatio } = useMolstarPlugin({
3812
+ const { addressIndexRef, chains: loadedChains, framedOrientationRef, isReady, loadCount, pluginRef, residuesByChainRef, sceneRef, sceneVersion, setClipRatio } = useMolstarPlugin({
4697
3813
  backgroundColor: bgColor,
4698
- chainColors,
4699
3814
  containerRef: pluginMountRef,
4700
3815
  download,
4701
3816
  edgeColor,
4702
- hasPlddt,
4703
3817
  hiddenChains,
4704
3818
  highlightColor,
4705
3819
  mode,
@@ -4707,23 +3821,52 @@ const ProteinStructureViewer = (0, react.forwardRef)((props, ref) => {
4707
3821
  onChainHover: highlightChain,
4708
3822
  onChainSelect: handleChainSelect,
4709
3823
  onChainToggle: toggleChain,
3824
+ onDispose,
3825
+ onError,
3826
+ onReady,
4710
3827
  onResidueClick,
4711
3828
  onResidueHover: handleResidueHover,
4712
3829
  onSelectionChange: changeSelection,
4713
3830
  onSelectionClear: handleSelectionClear,
3831
+ onStructureLoad,
3832
+ sceneProps: {
3833
+ chainColors: chainColorOverrides,
3834
+ colorBy: colorByProp,
3835
+ hiddenChains,
3836
+ highlights,
3837
+ initialCamera,
3838
+ mode,
3839
+ orientation,
3840
+ overlay: residueOverlay,
3841
+ plddt,
3842
+ projection,
3843
+ representation
3844
+ },
3845
+ sceneMode,
4714
3846
  selectedChains,
4715
3847
  sequenceViewerBackgroundColor,
4716
3848
  showAxes,
4717
3849
  showSequenceViewer,
4718
- structure: processedStructure
3850
+ structure
4719
3851
  });
4720
3852
  highlightChainRef.current = useChainHighlight({
4721
3853
  disabled: disableChainHighlightOnHover,
4722
3854
  hiddenChains,
4723
3855
  loadCount,
4724
3856
  pluginRef,
3857
+ sceneVersion,
4725
3858
  selectedChains
4726
3859
  });
3860
+ useCamera({
3861
+ framedOrientationRef,
3862
+ highlights,
3863
+ isReady,
3864
+ onCameraChange,
3865
+ orientation,
3866
+ pluginRef,
3867
+ projection,
3868
+ sceneRef
3869
+ });
4727
3870
  (0, react.useEffect)(() => {
4728
3871
  setChains(loadedChains);
4729
3872
  }, [loadedChains]);
@@ -4733,21 +3876,19 @@ const ProteinStructureViewer = (0, react.forwardRef)((props, ref) => {
4733
3876
  var _onChainsChangeRef$cu;
4734
3877
  (_onChainsChangeRef$cu = onChainsChangeRef.current) === null || _onChainsChangeRef$cu === void 0 || _onChainsChangeRef$cu.call(onChainsChangeRef, loadedChains);
4735
3878
  }, [loadedChains]);
4736
- useStructureColoring({
4737
- chainColorThemeRef,
4738
- chainColors,
4739
- hasPlddt,
4740
- isReady,
4741
- mode,
4742
- overlay: residueOverlay,
4743
- pluginRef,
4744
- residueValueThemeRef
4745
- });
3879
+ const focusedSelection = (0, react.useMemo)(() => {
3880
+ var _addressIndexRef$curr;
3881
+ return require_renderStructureImage.resolveSelectionAddresses(selection, (_addressIndexRef$curr = addressIndexRef.current) !== null && _addressIndexRef$curr !== void 0 ? _addressIndexRef$curr : /* @__PURE__ */ new Map());
3882
+ }, [
3883
+ selection,
3884
+ require_renderStructureImage.residueAddressesKey(selection === null || selection === void 0 ? void 0 : selection.addresses),
3885
+ loadCount
3886
+ ]);
4746
3887
  useSelectionFocus({
4747
3888
  hiddenChains,
4748
3889
  isReady,
4749
3890
  pluginRef,
4750
- selection,
3891
+ selection: focusedSelection,
4751
3892
  setClipRatio
4752
3893
  });
4753
3894
  const { hoveredReadout, selectedReadout } = useSelectionReadout({
@@ -4757,9 +3898,9 @@ const ProteinStructureViewer = (0, react.forwardRef)((props, ref) => {
4757
3898
  pluginRef,
4758
3899
  residueOverlay,
4759
3900
  residuesByChainRef,
4760
- selection
3901
+ selection: focusedSelection
4761
3902
  });
4762
- const scaleProps = (0, react.useMemo)(() => resolveScaleProps(residueOverlay, hasPlddt), [residueOverlay, hasPlddt]);
3903
+ const scaleProps = (0, react.useMemo)(() => resolveScaleProps(colorBy, residueOverlay), [colorBy, residueOverlay]);
4763
3904
  /**
4764
3905
  * What a chain's swatch can show follows whatever is painting the
4765
3906
  * structure, in the same order the color key resolves in.
@@ -4771,13 +3912,14 @@ const ProteinStructureViewer = (0, react.forwardRef)((props, ref) => {
4771
3912
  * a swatch at all, so the rows keep their labels and toggles and the color
4772
3913
  * key beside them does the describing.
4773
3914
  */
4774
- const chainColoringActive = !residueOverlay && !hasPlddt;
4775
- const plddtColoringActive = !residueOverlay && hasPlddt;
3915
+ const chainColoringActive = colorBy === "chain";
3916
+ const plddtColoringActive = colorBy === "plddt";
4776
3917
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ViewerRoot, require_objectSpread2._objectSpread2(require_objectSpread2._objectSpread2({
4777
3918
  ref,
3919
+ sequenceViewerHeight,
4778
3920
  showSequenceViewer
4779
3921
  }, rest), {}, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(PluginMount, { ref: pluginMountRef }), showLegend && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(StructureLegend, require_objectSpread2._objectSpread2({
4780
- chainBandColors: plddtColoringActive ? PLDDT_BAND_COLORS : void 0,
3922
+ chainBandColors: plddtColoringActive ? require_renderStructureImage.PLDDT_BAND_COLORS : void 0,
4781
3923
  chainColors: chainColoringActive ? chainColors : void 0,
4782
3924
  chains: showChainLegend ? chains : [],
4783
3925
  hiddenChains,
@@ -4787,6 +3929,7 @@ const ProteinStructureViewer = (0, react.forwardRef)((props, ref) => {
4787
3929
  onChainToggle: toggleChain,
4788
3930
  selectedChains,
4789
3931
  selectedResidue: selectedReadout,
3932
+ sequenceViewerHeight,
4790
3933
  showSequenceViewer,
4791
3934
  stats: stats !== null && stats !== void 0 ? stats : []
4792
3935
  }, scaleProps))] }));
@@ -4799,39 +3942,9 @@ Object.defineProperty(exports, "DOWNLOAD_RESOLUTIONS", {
4799
3942
  return DOWNLOAD_RESOLUTIONS;
4800
3943
  }
4801
3944
  });
4802
- Object.defineProperty(exports, "PLDDT_COLOR_SCALE", {
4803
- enumerable: true,
4804
- get: function() {
4805
- return PLDDT_COLOR_SCALE;
4806
- }
4807
- });
4808
3945
  Object.defineProperty(exports, "ProteinStructureViewer_default", {
4809
3946
  enumerable: true,
4810
3947
  get: function() {
4811
3948
  return ProteinStructureViewer_default;
4812
3949
  }
4813
3950
  });
4814
- Object.defineProperty(exports, "detectStructureFormat", {
4815
- enumerable: true,
4816
- get: function() {
4817
- return detectStructureFormat;
4818
- }
4819
- });
4820
- Object.defineProperty(exports, "injectPlddt", {
4821
- enumerable: true,
4822
- get: function() {
4823
- return injectPlddt;
4824
- }
4825
- });
4826
- Object.defineProperty(exports, "injectPlddtIntoMmcif", {
4827
- enumerable: true,
4828
- get: function() {
4829
- return injectPlddtIntoMmcif;
4830
- }
4831
- });
4832
- Object.defineProperty(exports, "injectPlddtIntoPdb", {
4833
- enumerable: true,
4834
- get: function() {
4835
- return injectPlddtIntoPdb;
4836
- }
4837
- });