jbrowse-plugin-protein3d 0.4.13 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/dist/LaunchProteinView/components/FoldseekActionMenu.js +15 -14
  2. package/dist/LaunchProteinView/components/ProteinViewActions.js +27 -15
  3. package/dist/LaunchProteinView/components/UserProvidedStructure.js +1 -2
  4. package/dist/LaunchProteinView/hooks/useSafeLaunch.d.ts +9 -0
  5. package/dist/LaunchProteinView/hooks/useSafeLaunch.js +15 -0
  6. package/dist/LaunchProteinView/utils/launchViewUtils.d.ts +9 -11
  7. package/dist/LaunchProteinView/utils/launchViewUtils.js +6 -8
  8. package/dist/LaunchProteinView/utils/sideBySide.d.ts +5 -0
  9. package/dist/LaunchProteinView/utils/sideBySide.js +9 -0
  10. package/dist/LaunchProteinViewExtensionPoint/index.js +7 -4
  11. package/dist/ProteinView/applyLociInteractivity.d.ts +23 -17
  12. package/dist/ProteinView/applyLociInteractivity.js +33 -61
  13. package/dist/ProteinView/components/FeatureBar.d.ts +1 -1
  14. package/dist/ProteinView/components/FeatureBar.js +36 -34
  15. package/dist/ProteinView/components/FeatureTypeLabel.js +5 -9
  16. package/dist/ProteinView/components/ProteinFeatureTrack.js +7 -15
  17. package/dist/ProteinView/components/ProteinViewHeader.js +9 -1
  18. package/dist/ProteinView/components/ResidueValueTrack.js +26 -15
  19. package/dist/ProteinView/hooks/useProteinFeatureTrackData.d.ts +1 -0
  20. package/dist/ProteinView/hooks/useProteinFeatureTrackData.js +3 -2
  21. package/dist/ProteinView/model.d.ts +12 -0
  22. package/dist/ProteinView/structureModel.d.ts +14 -5
  23. package/dist/ProteinView/structureModel.js +69 -92
  24. package/dist/ProteinView/subscribeMolstarInteraction.d.ts +8 -0
  25. package/dist/ProteinView/subscribeMolstarInteraction.js +1 -1
  26. package/dist/ProteinView/util.d.ts +0 -5
  27. package/dist/ProteinView/util.js +0 -11
  28. package/dist/jbrowse-plugin-protein3d.umd.production.min.js +15 -15
  29. package/dist/jbrowse-plugin-protein3d.umd.production.min.js.map +4 -4
  30. package/dist/version.d.ts +1 -1
  31. package/dist/version.js +1 -1
  32. package/package.json +1 -1
  33. package/src/LaunchProteinView/components/FoldseekActionMenu.tsx +22 -17
  34. package/src/LaunchProteinView/components/ProteinViewActions.tsx +32 -17
  35. package/src/LaunchProteinView/components/UserProvidedStructure.tsx +1 -6
  36. package/src/LaunchProteinView/hooks/useSafeLaunch.ts +17 -0
  37. package/src/LaunchProteinView/utils/launchViewUtils.ts +30 -29
  38. package/src/LaunchProteinView/utils/sideBySide.ts +14 -0
  39. package/src/LaunchProteinViewExtensionPoint/index.ts +8 -9
  40. package/src/ProteinView/applyLociInteractivity.ts +62 -114
  41. package/src/ProteinView/components/FeatureBar.tsx +40 -44
  42. package/src/ProteinView/components/FeatureTypeLabel.tsx +6 -11
  43. package/src/ProteinView/components/ProteinFeatureTrack.tsx +5 -17
  44. package/src/ProteinView/components/ProteinViewHeader.tsx +15 -0
  45. package/src/ProteinView/components/ResidueValueTrack.tsx +40 -23
  46. package/src/ProteinView/hooks/useProteinFeatureTrackData.ts +6 -2
  47. package/src/ProteinView/structureModel.ts +90 -108
  48. package/src/ProteinView/subscribeMolstarInteraction.ts +9 -1
  49. package/src/ProteinView/util.ts +0 -25
  50. package/src/version.ts +1 -1
  51. package/dist/ProteinView/highlightResidueRange.d.ts +0 -14
  52. package/dist/ProteinView/highlightResidueRange.js +0 -19
  53. package/src/ProteinView/highlightResidueRange.ts +0 -44
@@ -4,35 +4,27 @@ import { CHAR_WIDTH } from '../constants';
4
4
  import FeatureBar from './FeatureBar';
5
5
  import FeatureTypeLabel from './FeatureTypeLabel';
6
6
  import HoverMarker from './HoverMarker';
7
- function getVisibleTypes(featureTypes, hiddenFeatureTypes) {
8
- return featureTypes.filter(type => !hiddenFeatureTypes.has(type));
9
- }
10
7
  const FeatureTypeTrackContent = observer(function FeatureTypeTrackContent({ features, model, sequenceLength, }) {
11
8
  return (React.createElement("div", { style: {
12
9
  position: 'relative',
13
10
  height: model.trackHeight,
14
11
  width: sequenceLength * CHAR_WIDTH,
15
12
  marginBottom: model.trackGap,
16
- } },
17
- features.map(feature => (React.createElement(FeatureBar, { key: feature.uniqueId, feature: feature, model: model }))),
18
- React.createElement(HoverMarker, { model: model })));
13
+ } }, features.map(feature => (React.createElement(FeatureBar, { key: feature.uniqueId, feature: feature, model: model })))));
19
14
  });
20
15
  export const ProteinFeatureTrackLabels = observer(function ProteinFeatureTrackLabels({ data, labelWidth, model, }) {
21
- const { hiddenFeatureTypes } = model;
22
- const visibleTypes = getVisibleTypes(data.featureTypes, hiddenFeatureTypes);
23
- return (React.createElement(React.Fragment, null, visibleTypes.map(type => (React.createElement(FeatureTypeLabel, { key: type, type: type, labelWidth: labelWidth, model: model })))));
16
+ return (React.createElement(React.Fragment, null, data.visibleTypes.map(type => (React.createElement(FeatureTypeLabel, { key: type, type: type, labelWidth: labelWidth, model: model })))));
24
17
  });
25
18
  export const ProteinFeatureTrackContent = observer(function ProteinFeatureTrackContent({ data, model, }) {
26
- const { hiddenFeatureTypes } = model;
27
- const visibleTypes = getVisibleTypes(data.featureTypes, hiddenFeatureTypes);
28
- return (React.createElement("div", { onMouseMove: (e) => {
19
+ return (React.createElement("div", { style: { position: 'relative' }, onMouseMove: (e) => {
29
20
  const rect = e.currentTarget.getBoundingClientRect();
30
- const x = e.clientX - rect.left;
31
- const alignmentPos = Math.floor(x / CHAR_WIDTH);
21
+ const alignmentPos = Math.floor((e.clientX - rect.left) / CHAR_WIDTH);
32
22
  if (alignmentPos >= 0 && alignmentPos < data.sequenceLength) {
33
23
  model.hoverAlignmentPosition(alignmentPos);
34
24
  }
35
25
  }, onMouseLeave: () => {
36
26
  model.setHoveredPosition(undefined);
37
- } }, visibleTypes.map(type => (React.createElement(FeatureTypeTrackContent, { key: type, features: data.groupedFeatures[type], model: model, sequenceLength: data.sequenceLength })))));
27
+ } },
28
+ data.visibleTypes.map(type => (React.createElement(FeatureTypeTrackContent, { key: type, features: data.groupedFeatures[type], model: model, sequenceLength: data.sequenceLength }))),
29
+ React.createElement(HoverMarker, { model: model })));
38
30
  });
@@ -64,6 +64,7 @@ function getDisplayToggles(model) {
64
64
  }
65
65
  const DisplaySettingsMenu = observer(function DisplaySettingsMenu({ model, }) {
66
66
  const [anchorEl, setAnchorEl] = useState(null);
67
+ const hasHiddenTracks = model.structures.some(s => s.hiddenFeatureTypes.size > 0);
67
68
  return (React.createElement(React.Fragment, null,
68
69
  React.createElement(Tooltip, { title: "Display settings" },
69
70
  React.createElement(IconButton, { size: "small", onClick: event => {
@@ -72,7 +73,14 @@ const DisplaySettingsMenu = observer(function DisplaySettingsMenu({ model, }) {
72
73
  React.createElement(TuneIcon, { fontSize: "small" }))),
73
74
  React.createElement(Menu, { anchorEl: anchorEl, open: Boolean(anchorEl), onClose: () => {
74
75
  setAnchorEl(null);
75
- } }, getDisplayToggles(model).map(toggle => (React.createElement(ToggleMenuItem, { key: toggle.label, checked: toggle.checked, label: toggle.label, onToggle: toggle.onToggle }))))));
76
+ } },
77
+ getDisplayToggles(model).map(toggle => (React.createElement(ToggleMenuItem, { key: toggle.label, checked: toggle.checked, label: toggle.label, onToggle: toggle.onToggle }))),
78
+ hasHiddenTracks ? (React.createElement(MenuItem, { dense: true, onClick: () => {
79
+ for (const structure of model.structures) {
80
+ structure.showAllFeatureTypes();
81
+ }
82
+ } },
83
+ React.createElement(ListItemText, { inset: true }, "Restore hidden feature tracks"))) : null)));
76
84
  });
77
85
  const ProteinViewHeader = observer(function ProteinViewHeader({ model, }) {
78
86
  const { structures, showAlignment } = model;
@@ -1,4 +1,4 @@
1
- import React from 'react';
1
+ import React, { useMemo, useState } from 'react';
2
2
  import { Tooltip } from '@mui/material';
3
3
  import { observer } from 'mobx-react';
4
4
  import { CHAR_WIDTH } from '../constants';
@@ -8,21 +8,32 @@ import { CHAR_WIDTH } from '../constants';
8
8
  * tracks. Hovering drives the same structure hover as the feature tracks.
9
9
  */
10
10
  const ResidueValueTrack = observer(function ResidueValueTrack({ cells, colorFor, formatValue, sequenceLength, model, }) {
11
- return (React.createElement("div", { style: {
12
- position: 'relative',
13
- height: model.trackHeight,
14
- width: sequenceLength * CHAR_WIDTH,
15
- marginBottom: model.trackGap,
16
- }, onMouseMove: (e) => {
17
- const rect = e.currentTarget.getBoundingClientRect();
18
- const alignmentPos = Math.floor((e.clientX - rect.left) / CHAR_WIDTH);
19
- if (alignmentPos >= 0 && alignmentPos < sequenceLength) {
20
- model.hoverAlignmentPosition(alignmentPos);
21
- }
22
- }, onMouseLeave: () => {
23
- model.setHoveredPosition(undefined);
24
- } }, cells.map(cell => (React.createElement(Tooltip, { key: cell.col, title: formatValue(cell.value), followCursor: true },
11
+ const [hoveredCol, setHoveredCol] = useState(undefined);
12
+ const valueByCol = useMemo(() => {
13
+ const map = new Map();
14
+ for (const cell of cells) {
15
+ map.set(cell.col, cell.value);
16
+ }
17
+ return map;
18
+ }, [cells]);
19
+ const hoveredValue = hoveredCol === undefined ? undefined : valueByCol.get(hoveredCol);
20
+ return (React.createElement(Tooltip, { title: hoveredValue === undefined ? '' : formatValue(hoveredValue), followCursor: true },
25
21
  React.createElement("div", { style: {
22
+ position: 'relative',
23
+ height: model.trackHeight,
24
+ width: sequenceLength * CHAR_WIDTH,
25
+ marginBottom: model.trackGap,
26
+ }, onMouseMove: (e) => {
27
+ const rect = e.currentTarget.getBoundingClientRect();
28
+ const alignmentPos = Math.floor((e.clientX - rect.left) / CHAR_WIDTH);
29
+ setHoveredCol(alignmentPos);
30
+ if (alignmentPos >= 0 && alignmentPos < sequenceLength) {
31
+ model.hoverAlignmentPosition(alignmentPos);
32
+ }
33
+ }, onMouseLeave: () => {
34
+ setHoveredCol(undefined);
35
+ model.setHoveredPosition(undefined);
36
+ } }, cells.map(cell => (React.createElement("div", { key: cell.col, style: {
26
37
  position: 'absolute',
27
38
  left: cell.col * CHAR_WIDTH,
28
39
  top: 0,
@@ -3,6 +3,7 @@ import type { JBrowsePluginProteinStructureModel } from '../model';
3
3
  type FeaturesByType = Record<string, UniProtFeature[]>;
4
4
  export interface FeatureTrackData {
5
5
  featureTypes: string[];
6
+ visibleTypes: string[];
6
7
  groupedFeatures: FeaturesByType;
7
8
  sequenceLength: number;
8
9
  }
@@ -9,15 +9,16 @@ function groupFeaturesByType(features) {
9
9
  }
10
10
  export default function useProteinFeatureTrackData(model, uniprotId) {
11
11
  const { features, isLoading, error } = useUniProtFeatures(uniprotId);
12
- const { pairwiseAlignment } = model;
12
+ const { pairwiseAlignment, hiddenFeatureTypes } = model;
13
13
  if (!uniprotId || isLoading || error || !features || !pairwiseAlignment) {
14
14
  return { data: undefined, isLoading, error };
15
15
  }
16
16
  const sequenceLength = pairwiseAlignment.alns[0].seq.length;
17
17
  const groupedFeatures = groupFeaturesByType(features);
18
18
  const featureTypes = Object.keys(groupedFeatures);
19
+ const visibleTypes = featureTypes.filter(type => !hiddenFeatureTypes.has(type));
19
20
  return {
20
- data: { featureTypes, groupedFeatures, sequenceLength },
21
+ data: { featureTypes, visibleTypes, groupedFeatures, sequenceLength },
21
22
  isLoading: false,
22
23
  error: undefined,
23
24
  };
@@ -603,6 +603,10 @@ declare function stateModelFactory(): import("@jbrowse/mobx-state-tree").IModelT
603
603
  start: number;
604
604
  end: number;
605
605
  } | undefined;
606
+ readonly hoverHighlightRange: {
607
+ start: number;
608
+ end: number;
609
+ } | undefined;
606
610
  readonly clickAlignmentRange: {
607
611
  start: number;
608
612
  end: number;
@@ -1337,6 +1341,10 @@ declare function stateModelFactory(): import("@jbrowse/mobx-state-tree").IModelT
1337
1341
  start: number;
1338
1342
  end: number;
1339
1343
  } | undefined;
1344
+ readonly hoverHighlightRange: {
1345
+ start: number;
1346
+ end: number;
1347
+ } | undefined;
1340
1348
  readonly clickAlignmentRange: {
1341
1349
  start: number;
1342
1350
  end: number;
@@ -1950,6 +1958,10 @@ declare function stateModelFactory(): import("@jbrowse/mobx-state-tree").IModelT
1950
1958
  start: number;
1951
1959
  end: number;
1952
1960
  } | undefined;
1961
+ readonly hoverHighlightRange: {
1962
+ start: number;
1963
+ end: number;
1964
+ } | undefined;
1953
1965
  readonly clickAlignmentRange: {
1954
1966
  start: number;
1955
1967
  end: number;
@@ -262,6 +262,17 @@ declare const Structure: import("@jbrowse/mobx-state-tree").IModelType<{
262
262
  start: number;
263
263
  end: number;
264
264
  } | undefined;
265
+ /**
266
+ * #getter
267
+ * The current hover as a 0-based half-open structure range. A feature-range
268
+ * hover (hoverStructureRange) takes priority over a single-residue hover
269
+ * (structureSeqHoverPos). Drives both the molstar 3D highlight and the
270
+ * genome highlight.
271
+ */
272
+ readonly hoverHighlightRange: {
273
+ start: number;
274
+ end: number;
275
+ } | undefined;
265
276
  /**
266
277
  * #getter
267
278
  * Persistent click selection in alignment coordinates, derived from
@@ -282,11 +293,9 @@ declare const Structure: import("@jbrowse/mobx-state-tree").IModelType<{
282
293
  } | undefined): IRegion[];
283
294
  /**
284
295
  * #getter
285
- * Genome regions to highlight in the LGV based on the current hover.
286
- * A feature-range hover (hoverStructureRange) takes priority over a
287
- * single-residue hover (structureSeqHoverPos). Excludes hovers that
288
- * originated from the genome view itself, so hovering the LGV doesn't
289
- * echo a codon-width highlight back onto that same view.
296
+ * Genome regions to highlight in the LGV from the current hover. Excludes
297
+ * hovers that originated from the genome view itself, so hovering the LGV
298
+ * doesn't echo a codon-width highlight back onto that same view.
290
299
  */
291
300
  readonly hoverGenomeHighlights: IRegion[];
292
301
  /**
@@ -1,11 +1,10 @@
1
1
  import { SimpleFeature, getSession } from '@jbrowse/core/util';
2
2
  import { addDisposer, getParent, types, } from '@jbrowse/mobx-state-tree';
3
3
  import { autorun } from 'mobx';
4
- import { applyLociInteractivityMultiple, applyLociInteractivitySingle, } from './applyLociInteractivity';
4
+ import { setMolstarLoci } from './applyLociInteractivity';
5
5
  import { COMPACT_TRACK_GAP, COMPACT_TRACK_HEIGHT, NORMAL_TRACK_GAP, NORMAL_TRACK_HEIGHT, } from './constants';
6
6
  import { alignmentCol, makeCoordinateMapper, structurePos, } from './coordinates';
7
7
  import { looksLikePlddt } from './extractPerResidueConfidence';
8
- import highlightResidueRange from './highlightResidueRange';
9
8
  import { runLocalAlignment } from './pairwiseAlignment';
10
9
  import { proteinAbbreviationMapping } from './proteinAbbreviationMapping';
11
10
  import { clickProteinToGenome, proteinRangeToGenomeMapping, proteinToGenomeMapping, } from './proteinToGenomeMapping';
@@ -330,6 +329,18 @@ const Structure = types
330
329
  ? undefined
331
330
  : { start, end: end + 1 };
332
331
  },
332
+ /**
333
+ * #getter
334
+ * The current hover as a 0-based half-open structure range. A feature-range
335
+ * hover (hoverStructureRange) takes priority over a single-residue hover
336
+ * (structureSeqHoverPos). Drives both the molstar 3D highlight and the
337
+ * genome highlight.
338
+ */
339
+ get hoverHighlightRange() {
340
+ const pos = this.structureSeqHoverPos;
341
+ return (this.hoverStructureRange ??
342
+ (pos === undefined ? undefined : { start: pos, end: pos + 1 }));
343
+ },
333
344
  /**
334
345
  * #getter
335
346
  * Persistent click selection in alignment coordinates, derived from
@@ -378,27 +389,14 @@ const Structure = types
378
389
  },
379
390
  /**
380
391
  * #getter
381
- * Genome regions to highlight in the LGV based on the current hover.
382
- * A feature-range hover (hoverStructureRange) takes priority over a
383
- * single-residue hover (structureSeqHoverPos). Excludes hovers that
384
- * originated from the genome view itself, so hovering the LGV doesn't
385
- * echo a codon-width highlight back onto that same view.
392
+ * Genome regions to highlight in the LGV from the current hover. Excludes
393
+ * hovers that originated from the genome view itself, so hovering the LGV
394
+ * doesn't echo a codon-width highlight back onto that same view.
386
395
  */
387
396
  get hoverGenomeHighlights() {
388
- if (self.hoverPosition?.source === 'genome') {
389
- return [];
390
- }
391
- const range = this.hoverStructureRange;
392
- if (range) {
393
- return this.structureRangeToGenomeHighlight(range);
394
- }
395
- const structureSeqPos = this.structureSeqHoverPos;
396
- return structureSeqPos === undefined
397
+ return self.hoverPosition?.source === 'genome'
397
398
  ? []
398
- : this.structureRangeToGenomeHighlight({
399
- start: structureSeqPos,
400
- end: structureSeqPos + 1,
401
- });
399
+ : this.structureRangeToGenomeHighlight(this.hoverHighlightRange);
402
400
  },
403
401
  /**
404
402
  * #getter
@@ -564,6 +562,20 @@ const Structure = types
564
562
  }))
565
563
  .actions(self => ({
566
564
  afterAttach() {
565
+ // Re-subscribe to a molstar click/hover behavior whenever the plugin
566
+ // becomes available; the subscription is disposed with the model.
567
+ const addInteractionListener = (kind, onUpdate) => {
568
+ addDisposer(self, autorun(async () => {
569
+ const { molstarPluginContext } = self;
570
+ if (molstarPluginContext) {
571
+ addDisposer(self, await subscribeMolstarInteraction({
572
+ plugin: molstarPluginContext,
573
+ kind,
574
+ onUpdate,
575
+ }));
576
+ }
577
+ }));
578
+ };
567
579
  addDisposer(self, autorun(async () => {
568
580
  try {
569
581
  const { userProvidedTranscriptSequence, structureSequences, exactMatch, alignmentAlgorithm, } = self;
@@ -611,89 +623,54 @@ const Structure = types
611
623
  self.setGenomeHoveredPosition(undefined);
612
624
  }
613
625
  }));
614
- addDisposer(self, autorun(async () => {
615
- const { molstarPluginContext } = self;
616
- if (molstarPluginContext) {
617
- const dispose = await subscribeMolstarInteraction({
618
- plugin: molstarPluginContext,
619
- kind: 'click',
620
- onUpdate: info => {
621
- // Click only acts on positive matches; ignore clicks that
622
- // didn't land on a structure element.
623
- if (!info) {
624
- return;
625
- }
626
- self.setHoveredPosition(info);
627
- self.setSelectedFeatureId(undefined);
628
- clickProteinToGenome({
629
- model: self,
630
- structureSeqPos: info.structureSeqPos,
631
- }).catch((e) => {
632
- console.error(e);
633
- self.parentView.setError(e);
634
- });
635
- },
626
+ // Click only acts on positive matches; clicks that didn't land on a
627
+ // structure element are ignored.
628
+ addInteractionListener('click', info => {
629
+ if (info) {
630
+ self.setHoveredPosition(info);
631
+ self.setSelectedFeatureId(undefined);
632
+ clickProteinToGenome({
633
+ model: self,
634
+ structureSeqPos: info.structureSeqPos,
635
+ }).catch((e) => {
636
+ console.error(e);
637
+ self.parentView.setError(e);
636
638
  });
637
- addDisposer(self, dispose);
638
639
  }
639
- }));
640
- addDisposer(self, autorun(async () => {
641
- const { molstarPluginContext } = self;
642
- if (molstarPluginContext) {
643
- const dispose = await subscribeMolstarInteraction({
644
- plugin: molstarPluginContext,
645
- kind: 'hover',
646
- onUpdate: info => {
647
- self.setHoveredPosition(info);
648
- },
649
- });
650
- addDisposer(self, dispose);
651
- }
652
- }));
640
+ });
641
+ addInteractionListener('hover', info => {
642
+ self.setHoveredPosition(info);
643
+ });
653
644
  addDisposer(self, autorun(async () => {
654
645
  const { showHighlight, structureSeqToTranscriptSeqPosition, molstarPluginContext, molstarStructure, } = self;
655
646
  if (molstarStructure &&
656
647
  molstarPluginContext &&
657
648
  structureSeqToTranscriptSeqPosition) {
658
- if (showHighlight) {
659
- const residues = Object.keys(structureSeqToTranscriptSeqPosition).map(coord => +coord + 1);
660
- await applyLociInteractivityMultiple({
661
- structure: molstarStructure,
662
- residues,
663
- plugin: molstarPluginContext,
664
- mode: 'select',
665
- });
666
- }
667
- else {
668
- molstarPluginContext.managers.interactivity.lociSelects.deselectAll();
669
- }
649
+ await setMolstarLoci({
650
+ structure: molstarStructure,
651
+ plugin: molstarPluginContext,
652
+ channel: 'select',
653
+ spec: showHighlight
654
+ ? {
655
+ kind: 'list',
656
+ residues: Object.keys(structureSeqToTranscriptSeqPosition).map(coord => +coord),
657
+ }
658
+ : undefined,
659
+ });
670
660
  }
671
661
  }));
672
- // Drive molstar hover-highlight state from the model. A feature-range
673
- // hover (hoverStructureRange) takes priority over a single-residue
674
- // hover (structureSeqHoverPos); otherwise clear.
662
+ // Drive molstar hover-highlight from the model's hoverHighlightRange.
675
663
  addDisposer(self, autorun(async () => {
676
- const { molstarStructure, molstarPluginContext, hoverStructureRange, structureSeqHoverPos, } = self;
664
+ const { molstarStructure, molstarPluginContext, hoverHighlightRange } = self;
677
665
  if (molstarStructure && molstarPluginContext) {
678
- if (hoverStructureRange) {
679
- await highlightResidueRange({
680
- structure: molstarStructure,
681
- plugin: molstarPluginContext,
682
- startResidue: hoverStructureRange.start + 1,
683
- endResidue: hoverStructureRange.end,
684
- });
685
- }
686
- else if (structureSeqHoverPos !== undefined) {
687
- await applyLociInteractivitySingle({
688
- structure: molstarStructure,
689
- plugin: molstarPluginContext,
690
- selectedResidue: structureSeqHoverPos,
691
- mode: 'highlight',
692
- });
693
- }
694
- else {
695
- molstarPluginContext.managers.interactivity.lociHighlights.clearHighlights();
696
- }
666
+ await setMolstarLoci({
667
+ structure: molstarStructure,
668
+ plugin: molstarPluginContext,
669
+ channel: 'highlight',
670
+ spec: hoverHighlightRange
671
+ ? { kind: 'range', ...hoverHighlightRange }
672
+ : undefined,
673
+ });
697
674
  }
698
675
  }));
699
676
  },
@@ -1,5 +1,13 @@
1
1
  import type { PluginContext } from 'molstar/lib/mol-plugin/context';
2
2
  export interface MolstarLocationInfo {
3
+ /**
4
+ * 0-based label position (label_seq_id - 1). This is the plugin's canonical
5
+ * structure coordinate: structureSequences, the coordinate maps, and the
6
+ * outbound highlight in setMolstarLoci are all label-based, so the inbound
7
+ * read must be too. For AlphaFold structures label_seq_id == auth_seq_id, but
8
+ * for PDB structures whose author numbering is offset or gapped they diverge,
9
+ * and reading auth_seq_id here would mis-map every hover/click.
10
+ */
3
11
  structureSeqPos: number;
4
12
  code: string;
5
13
  chain: string;
@@ -1,7 +1,7 @@
1
1
  import loadMolstar from './loadMolstar';
2
2
  function extractLocationInfo(molstar, location) {
3
3
  return {
4
- structureSeqPos: molstar.StructureProperties.residue.auth_seq_id(location) - 1,
4
+ structureSeqPos: molstar.StructureProperties.residue.label_seq_id(location) - 1,
5
5
  code: molstar.StructureProperties.atom.label_comp_id(location),
6
6
  chain: molstar.StructureProperties.chain.auth_asym_id(location),
7
7
  };
@@ -1,4 +1,3 @@
1
- import type { Structure } from 'molstar/lib/mol-model/structure';
2
1
  interface HoveredState {
3
2
  hoverPosition: {
4
3
  coord: number;
@@ -6,9 +5,5 @@ interface HoveredState {
6
5
  };
7
6
  }
8
7
  export declare function checkHovered(hovered: unknown): hovered is HoveredState;
9
- export declare function getMolstarStructureSelection({ structure, selectedResidue, }: {
10
- structure: Structure;
11
- selectedResidue: number;
12
- }): Promise<import("molstar/lib/mol-model/structure").StructureSelection>;
13
8
  export declare function invertMap(arg: Record<number, number>): Record<number, number>;
14
9
  export {};
@@ -1,4 +1,3 @@
1
- import loadMolstar from './loadMolstar';
2
1
  export function checkHovered(hovered) {
3
2
  return (!!hovered &&
4
3
  typeof hovered === 'object' &&
@@ -8,16 +7,6 @@ export function checkHovered(hovered) {
8
7
  'coord' in hovered.hoverPosition &&
9
8
  'refName' in hovered.hoverPosition);
10
9
  }
11
- export async function getMolstarStructureSelection({ structure, selectedResidue, }) {
12
- const { Script } = await loadMolstar();
13
- return Script.getStructureSelection(Q => Q.struct.generator.atomGroups({
14
- 'residue-test': Q.core.rel.eq([
15
- Q.struct.atomProperty.macromolecular.label_seq_id(),
16
- selectedResidue,
17
- ]),
18
- 'group-by': Q.struct.atomProperty.macromolecular.residueKey(),
19
- }), structure);
20
- }
21
10
  export function invertMap(arg) {
22
11
  return Object.fromEntries(Object.entries(arg).map(([a, b]) => [b, +a]));
23
12
  }