jbrowse-plugin-protein3d 0.5.0 → 0.5.2

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.
@@ -7,7 +7,7 @@ export default function LaunchProteinViewExtensionPointF(pluginManager) {
7
7
  // handlers and ignores the return value. Casting away the signature
8
8
  // mismatch rather than fabricating a fake return.
9
9
  // @ts-expect-error
10
- async ({ session, url, uniprotId, transcriptId, userProvidedTranscriptSequence, feature, connectedViewId, connectedView, alignmentAlgorithm, displayName, height, showControls, showHighlight, zoomToBaseLevel, sideBySide, }) => {
10
+ async ({ session, url, uniprotId, transcriptId, userProvidedTranscriptSequence, feature, connectedViewId, connectedView, alignmentAlgorithm, displayName, height, showControls, showHighlight, zoomToBaseLevel, sideBySide, initialSelection, }) => {
11
11
  // Short-URL form: `uniprotId` + `transcriptId` + `connectedView` (no
12
12
  // explicit `url`/`feature`/sequence). Derive the structure URL, the
13
13
  // transcript feature, and the translated sequence from the connected
@@ -67,6 +67,7 @@ export default function LaunchProteinViewExtensionPointF(pluginManager) {
67
67
  '',
68
68
  feature: resolved?.feature ?? feature,
69
69
  connectedViewId: resolvedConnectedViewId,
70
+ initialSelection,
70
71
  },
71
72
  ],
72
73
  });
@@ -27,6 +27,7 @@ function GutterLabel({ label, title, height, }) {
27
27
  const ProteinAlignment = observer(function ProteinAlignment({ model, }) {
28
28
  const { pairwiseAlignment, showHighlight, showProteinTracks, uniprotId, confidenceCells, hydrophobicityCells, } = model;
29
29
  const containerRef = useRef(null);
30
+ const scrolledToSelectionRef = useRef(false);
30
31
  const { data: featureData, isLoading: featureLoading, error: featureError, } = useProteinFeatureTrackData(model, uniprotId);
31
32
  useEffect(() => autorun(() => {
32
33
  const container = containerRef.current;
@@ -40,6 +41,19 @@ const ProteinAlignment = observer(function ProteinAlignment({ model, }) {
40
41
  });
41
42
  }
42
43
  }), [model]);
44
+ // Scroll the persistent selection into view once it first resolves, so a
45
+ // declarative `initialSelection` (or any off-screen selection) shows its
46
+ // highlight band instead of opening scrolled to the N-terminus. Guarded to
47
+ // fire once, so it doesn't fight the user's own scrolling afterward.
48
+ useEffect(() => autorun(() => {
49
+ const container = containerRef.current;
50
+ const range = model.clickAlignmentRange;
51
+ if (container && range && !scrolledToSelectionRef.current) {
52
+ scrolledToSelectionRef.current = true;
53
+ const mid = ((range.start + range.end) / 2) * CHAR_WIDTH;
54
+ container.scrollTo({ left: mid - container.clientWidth / 2 });
55
+ }
56
+ }), [model]);
43
57
  if (!pairwiseAlignment) {
44
58
  return React.createElement("div", null, "No pairwiseAlignment");
45
59
  }
@@ -29,6 +29,16 @@ declare function stateModelFactory(): import("@jbrowse/mobx-state-tree").IModelT
29
29
  pairwiseAlignment: import("@jbrowse/mobx-state-tree").IType<import("../mappings").PairwiseAlignment | undefined, import("../mappings").PairwiseAlignment | undefined, import("../mappings").PairwiseAlignment | undefined>;
30
30
  feature: import("@jbrowse/mobx-state-tree").IType<import("@jbrowse/core/util").SimpleFeatureSerialized | undefined, import("@jbrowse/core/util").SimpleFeatureSerialized | undefined, import("@jbrowse/core/util").SimpleFeatureSerialized | undefined>;
31
31
  userProvidedTranscriptSequence: import("@jbrowse/mobx-state-tree").ISimpleType<string>;
32
+ initialSelection: import("@jbrowse/mobx-state-tree").IType<{
33
+ start: number;
34
+ end: number;
35
+ } | undefined, {
36
+ start: number;
37
+ end: number;
38
+ } | undefined, {
39
+ start: number;
40
+ end: number;
41
+ } | undefined>;
32
42
  }, {
33
43
  clickedStructureRange: {
34
44
  start: number;
@@ -767,6 +777,16 @@ declare function stateModelFactory(): import("@jbrowse/mobx-state-tree").IModelT
767
777
  pairwiseAlignment: import("@jbrowse/mobx-state-tree").IType<import("../mappings").PairwiseAlignment | undefined, import("../mappings").PairwiseAlignment | undefined, import("../mappings").PairwiseAlignment | undefined>;
768
778
  feature: import("@jbrowse/mobx-state-tree").IType<import("@jbrowse/core/util").SimpleFeatureSerialized | undefined, import("@jbrowse/core/util").SimpleFeatureSerialized | undefined, import("@jbrowse/core/util").SimpleFeatureSerialized | undefined>;
769
779
  userProvidedTranscriptSequence: import("@jbrowse/mobx-state-tree").ISimpleType<string>;
780
+ initialSelection: import("@jbrowse/mobx-state-tree").IType<{
781
+ start: number;
782
+ end: number;
783
+ } | undefined, {
784
+ start: number;
785
+ end: number;
786
+ } | undefined, {
787
+ start: number;
788
+ end: number;
789
+ } | undefined>;
770
790
  }> & {
771
791
  clickedStructureRange: {
772
792
  start: number;
@@ -1384,6 +1404,16 @@ declare function stateModelFactory(): import("@jbrowse/mobx-state-tree").IModelT
1384
1404
  pairwiseAlignment: import("@jbrowse/mobx-state-tree").IType<import("../mappings").PairwiseAlignment | undefined, import("../mappings").PairwiseAlignment | undefined, import("../mappings").PairwiseAlignment | undefined>;
1385
1405
  feature: import("@jbrowse/mobx-state-tree").IType<import("@jbrowse/core/util").SimpleFeatureSerialized | undefined, import("@jbrowse/core/util").SimpleFeatureSerialized | undefined, import("@jbrowse/core/util").SimpleFeatureSerialized | undefined>;
1386
1406
  userProvidedTranscriptSequence: import("@jbrowse/mobx-state-tree").ISimpleType<string>;
1407
+ initialSelection: import("@jbrowse/mobx-state-tree").IType<{
1408
+ start: number;
1409
+ end: number;
1410
+ } | undefined, {
1411
+ start: number;
1412
+ end: number;
1413
+ } | undefined, {
1414
+ start: number;
1415
+ end: number;
1416
+ } | undefined>;
1387
1417
  }, {
1388
1418
  clickedStructureRange: {
1389
1419
  start: number;
@@ -48,6 +48,24 @@ declare const Structure: import("@jbrowse/mobx-state-tree").IModelType<{
48
48
  * #property
49
49
  */
50
50
  userProvidedTranscriptSequence: import("@jbrowse/mobx-state-tree").ISimpleType<string>;
51
+ /**
52
+ * #property
53
+ * Declarative seed for the persistent domain selection: a 0-based,
54
+ * half-open structure-residue range `{ start, end }` lit on load exactly as
55
+ * if the user had clicked that domain — magenta in the 3D structure, a band
56
+ * on the connected genome view, and the range in the alignment. Lets a
57
+ * session spec open with a domain pre-highlighted, with no click.
58
+ */
59
+ initialSelection: import("@jbrowse/mobx-state-tree").IType<{
60
+ start: number;
61
+ end: number;
62
+ } | undefined, {
63
+ start: number;
64
+ end: number;
65
+ } | undefined, {
66
+ start: number;
67
+ end: number;
68
+ } | undefined>;
51
69
  }, {
52
70
  /**
53
71
  * #volatile
@@ -41,6 +41,15 @@ const Structure = types
41
41
  * #property
42
42
  */
43
43
  userProvidedTranscriptSequence: types.string,
44
+ /**
45
+ * #property
46
+ * Declarative seed for the persistent domain selection: a 0-based,
47
+ * half-open structure-residue range `{ start, end }` lit on load exactly as
48
+ * if the user had clicked that domain — magenta in the 3D structure, a band
49
+ * on the connected genome view, and the range in the alignment. Lets a
50
+ * session spec open with a domain pre-highlighted, with no click.
51
+ */
52
+ initialSelection: types.frozen(),
44
53
  })
45
54
  .volatile(() => ({
46
55
  /**
@@ -562,6 +571,16 @@ const Structure = types
562
571
  }))
563
572
  .actions(self => ({
564
573
  afterAttach() {
574
+ // Seed the persistent selection from a declarative `initialSelection`, so
575
+ // a session spec can open with a domain pre-lit. clickedStructureRange is
576
+ // the single source of truth the 3D/genome/alignment highlights derive
577
+ // from; the genome-band and alignment getters recompute reactively once
578
+ // the connected view + mapping resolve, and the molstar select autorun
579
+ // below lights it once the structure loads. A later user click overwrites
580
+ // it normally.
581
+ if (self.initialSelection) {
582
+ self.setClickedStructureRange(self.initialSelection);
583
+ }
565
584
  // Re-subscribe to a molstar click/hover behavior whenever the plugin
566
585
  // becomes available; the subscription is disposed with the model.
567
586
  const addInteractionListener = (kind, onUpdate) => {
@@ -641,8 +660,15 @@ const Structure = types
641
660
  addInteractionListener('hover', info => {
642
661
  self.setHoveredPosition(info);
643
662
  });
663
+ // Drive the molstar 'select' channel (the persistent magenta selection)
664
+ // reactively from a single source of truth: a clicked/declarative domain
665
+ // range takes priority, else the whole alignment-covered set when
666
+ // showHighlight is on, else nothing. Centralizing it here (rather than
667
+ // only applying the clicked range imperatively from the feature bar) lets
668
+ // a declarative `initialSelection` seed light the 3D structure the same
669
+ // way a click does, with no race against this autorun.
644
670
  addDisposer(self, autorun(async () => {
645
- const { showHighlight, structureSeqToTranscriptSeqPosition, molstarPluginContext, molstarStructure, } = self;
671
+ const { showHighlight, clickedStructureRange, structureSeqToTranscriptSeqPosition, molstarPluginContext, molstarStructure, } = self;
646
672
  if (molstarStructure &&
647
673
  molstarPluginContext &&
648
674
  structureSeqToTranscriptSeqPosition) {
@@ -650,12 +676,14 @@ const Structure = types
650
676
  structure: molstarStructure,
651
677
  plugin: molstarPluginContext,
652
678
  channel: 'select',
653
- spec: showHighlight
654
- ? {
655
- kind: 'list',
656
- residues: Object.keys(structureSeqToTranscriptSeqPosition).map(coord => +coord),
657
- }
658
- : undefined,
679
+ spec: clickedStructureRange
680
+ ? { kind: 'range', ...clickedStructureRange }
681
+ : showHighlight
682
+ ? {
683
+ kind: 'list',
684
+ residues: Object.keys(structureSeqToTranscriptSeqPosition).map(coord => +coord),
685
+ }
686
+ : undefined,
659
687
  });
660
688
  }
661
689
  }));
@@ -17,7 +17,7 @@ export default function useProteinView({ showControls, model, }) {
17
17
  if (!parentRef.current) {
18
18
  return;
19
19
  }
20
- const { GeometryExport, MAQualityAssessment, PluginConfig, PluginSpec, DefaultPluginUISpec, createPluginUI, renderReact18, } = await loadMolstar();
20
+ const { Color, GeometryExport, MAQualityAssessment, PluginConfig, PluginSpec, DefaultPluginUISpec, createPluginUI, renderReact18, } = await loadMolstar();
21
21
  const host = document.createElement('div');
22
22
  parentRef.current.append(host);
23
23
  state.host = host;
@@ -44,6 +44,14 @@ export default function useProteinView({ showControls, model, }) {
44
44
  },
45
45
  });
46
46
  await created.initialized;
47
+ // Make the persistent selection marking clearly visible (a domain
48
+ // click, or a declarative initialSelection): molstar's default select
49
+ // color is a subtle teal that blends into a green/pLDDT cartoon, so a
50
+ // highlighted domain reads as no highlight at all. Magenta contrasts
51
+ // against every built-in color scheme.
52
+ created.canvas3d?.setProps({
53
+ renderer: { selectColor: Color(0xff00ff) },
54
+ });
47
55
  if (state.cancelled) {
48
56
  created.dispose();
49
57
  host.remove();