jbrowse-plugin-protein3d 0.16.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/dist/AddHighlightModel/Highlight.js +2 -2
  2. package/dist/AddHighlightModel/index.js +2 -11
  3. package/dist/AddHighlightModel/util.js +0 -20
  4. package/dist/LaunchProteinView/codingFeature.js +1 -15
  5. package/dist/LaunchProteinView/components/proteinTrackSetup.js +17 -37
  6. package/dist/LaunchProteinView/components/wiggleBandColors.js +3 -17
  7. package/dist/LaunchProteinView/index.js +27 -55
  8. package/dist/LaunchProteinView/utils/launchViewUtils.js +5 -3
  9. package/dist/LaunchProteinView/utils/sideBySide.js +6 -29
  10. package/dist/LaunchProteinView/utils/translateTranscripts.js +3 -12
  11. package/dist/LaunchProteinView/utils/util.js +2 -1
  12. package/dist/LaunchProteinViewExtensionPoint/index.js +4 -7
  13. package/dist/LaunchProteinViewExtensionPoint/resolveShortLaunch.js +3 -23
  14. package/dist/Protein1DLinkage/index.js +1 -2
  15. package/dist/ProteinView/alignOffThread.js +1 -5
  16. package/dist/ProteinView/components/ProteinViewHeader.js +2 -2
  17. package/dist/chunks/{HelpDialog-KAG73BNB.js → HelpDialog-I4UK7TQB.js} +2 -2
  18. package/dist/chunks/{ProteinAlignmentHelpDialog-L2GH5YTM.js → ProteinAlignmentHelpDialog-6VFZFN6X.js} +2 -2
  19. package/dist/chunks/{ProteinView-7GUUQZL4.js → ProteinView-5UGGQVH6.js} +5 -5
  20. package/dist/chunks/ProteinView-5UGGQVH6.js.map +7 -0
  21. package/dist/jbrowse-plugin-protein3d.esm.js +7 -7
  22. package/dist/jbrowse-plugin-protein3d.esm.js.map +4 -4
  23. package/dist/version.js +1 -1
  24. package/package.json +1 -1
  25. package/src/AddHighlightModel/Highlight.tsx +2 -2
  26. package/src/AddHighlightModel/index.test.ts +2 -5
  27. package/src/AddHighlightModel/index.tsx +4 -17
  28. package/src/AddHighlightModel/util.ts +0 -30
  29. package/src/LaunchProteinView/codingFeature.test.ts +1 -44
  30. package/src/LaunchProteinView/codingFeature.ts +2 -22
  31. package/src/LaunchProteinView/components/launchProteinAnnotationView.ts +2 -3
  32. package/src/LaunchProteinView/components/proteinAssemblySetup.ts +2 -2
  33. package/src/LaunchProteinView/components/proteinTrackSetup.ts +24 -47
  34. package/src/LaunchProteinView/components/wiggleBandColors.test.ts +3 -30
  35. package/src/LaunchProteinView/components/wiggleBandColors.ts +3 -22
  36. package/src/LaunchProteinView/index.ts +39 -74
  37. package/src/LaunchProteinView/menuTarget.test.ts +7 -14
  38. package/src/LaunchProteinView/utils/launchViewUtils.ts +12 -6
  39. package/src/LaunchProteinView/utils/sideBySide.test.ts +0 -6
  40. package/src/LaunchProteinView/utils/sideBySide.ts +6 -29
  41. package/src/LaunchProteinView/utils/translateTranscripts.ts +3 -15
  42. package/src/LaunchProteinView/utils/util.ts +2 -1
  43. package/src/LaunchProteinViewExtensionPoint/index.ts +4 -13
  44. package/src/LaunchProteinViewExtensionPoint/resolveShortLaunch.ts +6 -33
  45. package/src/Protein1DLinkage/index.ts +1 -2
  46. package/src/ProteinView/alignOffThread.ts +1 -5
  47. package/src/ProteinView/components/ProteinViewHeader.tsx +0 -2
  48. package/src/version.ts +1 -1
  49. package/dist/LaunchProteinView/utils/sessionWithAddTracks.js +0 -6
  50. package/dist/chunks/ProteinView-7GUUQZL4.js.map +0 -7
  51. package/dist/extendStateModel.js +0 -9
  52. package/src/LaunchProteinView/utils/sessionWithAddTracks.ts +0 -15
  53. package/src/extendStateModel.ts +0 -29
  54. /package/dist/chunks/{HelpDialog-KAG73BNB.js.map → HelpDialog-I4UK7TQB.js.map} +0 -0
  55. /package/dist/chunks/{ProteinAlignmentHelpDialog-L2GH5YTM.js.map → ProteinAlignmentHelpDialog-6VFZFN6X.js.map} +0 -0
@@ -1,9 +1,9 @@
1
1
  import React from 'react';
2
2
  import { observer } from 'mobx-react';
3
- import { getHighlightCoords, useStyles } from './util';
3
+ import { useStyles } from './util';
4
4
  const Highlight = observer(function Highlight({ region, model, }) {
5
5
  const { cx, classes } = useStyles();
6
- const coords = getHighlightCoords(model, region);
6
+ const coords = model.getHighlightCoords(region);
7
7
  return coords ? (React.createElement("div", { className: cx(classes.highlight, coords.width <= 3 ? classes.thinborder : undefined), style: { left: coords.left, width: coords.width } })) : null;
8
8
  });
9
9
  export default Highlight;
@@ -1,14 +1,5 @@
1
- import React from 'react';
1
+ import { addExtensionElement } from '@jbrowse/core/ui';
2
2
  import HighlightComponents from './HighlightComponents';
3
3
  export default function AddHighlightModelF(pluginManager) {
4
- pluginManager.addToExtensionPoint(
5
- // @ts-expect-error v4 hosts have no contributeToExtensionPoint
6
- 'LinearGenomeView-TracksContainerComponent',
7
- // v4 hosts seed this point with undefined rather than []
8
- (rest, { model }) => {
9
- return [
10
- ...(rest ?? []),
11
- React.createElement(HighlightComponents, { key: "highlight_protein_viewer_protein3d", model: model }),
12
- ];
13
- });
4
+ addExtensionElement(pluginManager, 'LinearGenomeView-TracksContainerComponent', HighlightComponents);
14
5
  }
@@ -1,24 +1,4 @@
1
- import { getSession } from '@jbrowse/core/util';
2
1
  import { makeStyles } from 'tss-react/mui';
3
- // Local mirror of core's getLayoutHighlightCoords / model.getHighlightCoords,
4
- // added to jbrowse after 4.3.0. Kept here so the plugin still builds against
5
- // 4.3.0; once the minimum jbrowse version ships the model method, replace the
6
- // body with `model.getHighlightCoords(region)`.
7
- export function getHighlightCoords(model, region) {
8
- const { assemblyManager } = getSession(model);
9
- const assembly = region.assemblyName
10
- ? assemblyManager.get(region.assemblyName)
11
- : undefined;
12
- const refName = assembly?.getCanonicalRefName(region.refName) ?? region.refName;
13
- const s = model.bpToPx({ refName, coord: region.start });
14
- const e = model.bpToPx({ refName, coord: region.end });
15
- return s && e
16
- ? {
17
- width: Math.max(Math.abs(e.offsetPx - s.offsetPx), 3),
18
- left: Math.min(s.offsetPx, e.offsetPx) - model.offsetPx,
19
- }
20
- : undefined;
21
- }
22
2
  export const useStyles = makeStyles()({
23
3
  highlight: {
24
4
  height: '100%',
@@ -1,9 +1,4 @@
1
- // Copied from @jbrowse/core's featureTypes: the barrel export reads undefined
2
- // on a v4 host, and the deep path is not in core's exports map.
3
- const GENE_LIKE_TYPE = /gene(_segment)?$|rna$|transcript/;
4
- export function isGeneLikeType(type) {
5
- return type !== undefined && GENE_LIKE_TYPE.test(type.toLowerCase());
6
- }
1
+ import { isGeneLikeType } from '@jbrowse/core/util';
7
2
  function isCDS(feature) {
8
3
  return feature.get('type')?.toLowerCase() === 'cds';
9
4
  }
@@ -22,12 +17,3 @@ export function codingTranscripts(feature) {
22
17
  export function isCodingFeature(feature) {
23
18
  return codingTranscripts(feature).length > 0;
24
19
  }
25
- // The outermost gene-like ancestor, so a click on an isoform opens the dialog
26
- // on the gene with every transcript to choose from, as the canvas host does.
27
- export function geneLikeRoot(feature) {
28
- let root = feature;
29
- for (let parent = root.parent?.(); parent && isGeneLikeType(parent.get('type')); parent = parent.parent?.()) {
30
- root = parent;
31
- }
32
- return root;
33
- }
@@ -1,6 +1,5 @@
1
- import { getEnv } from '@jbrowse/mobx-state-tree';
2
1
  import { myfetch, uniprotGffUrl } from 'p2s_mapper';
3
- import { jexlBandColor, thresholdBandColor } from './wiggleBandColors';
2
+ import { thresholdBandColor } from './wiggleBandColors';
4
3
  import { PLDDT_BANDS } from '../../ProteinView/residueTracks';
5
4
  /**
6
5
  * Fetches UniProt GFF data and extracts unique feature types
@@ -23,7 +22,7 @@ async function fetchUniProtFeatureTypes(uniprotId) {
23
22
  function addUniProtFeatureTracks({ session, uniprotId, featureTypes, }) {
24
23
  featureTypes.forEach(type => {
25
24
  const trackId = `${uniprotId}-${type}`;
26
- session.addTrackConf({
25
+ session.addSessionTrackConf({
27
26
  type: 'FeatureTrack',
28
27
  trackId,
29
28
  name: type,
@@ -48,7 +47,7 @@ function addUniProtFeatureTracks({ session, uniprotId, featureTypes, }) {
48
47
  * Adds antigen annotation track from EBI
49
48
  */
50
49
  function addAntigenTrack({ session, uniprotId, }) {
51
- session.addTrackConf({
50
+ session.addSessionTrackConf({
52
51
  type: 'FeatureTrack',
53
52
  trackId: `${uniprotId}-Antigen`,
54
53
  name: 'Antigen',
@@ -65,7 +64,7 @@ function addAntigenTrack({ session, uniprotId, }) {
65
64
  * Adds variation track from EBI
66
65
  */
67
66
  function addVariationTrack({ session, uniprotId, }) {
68
- session.addTrackConf({
67
+ session.addSessionTrackConf({
69
68
  type: 'FeatureTrack',
70
69
  trackId: `${uniprotId}-Variation`,
71
70
  name: 'Variation',
@@ -83,7 +82,7 @@ function addVariationTrack({ session, uniprotId, }) {
83
82
  */
84
83
  function addAlphaFoldConfidenceTrack({ session, uniprotId, confidenceUrl, }) {
85
84
  if (confidenceUrl) {
86
- session.addTrackConf({
85
+ session.addSessionTrackConf({
87
86
  type: 'QuantitativeTrack',
88
87
  trackId: `${uniprotId}-AlphaFold-confidence`,
89
88
  name: 'AlphaFold confidence',
@@ -98,12 +97,6 @@ function addAlphaFoldConfidenceTrack({ session, uniprotId, confidenceUrl, }) {
98
97
  {
99
98
  type: 'LinearWiggleDisplay',
100
99
  displayId: `${uniprotId}-AlphaFold-confidence-LinearWiggleDisplay`,
101
- // Each host drops the key it does not declare: v4 reads the
102
- // renderers, v5 the display's `color`.
103
- renderers: {
104
- XYPlotRenderer: { color: jexlBandColor('score', PLDDT_BANDS) },
105
- DensityRenderer: { color: jexlBandColor('score', PLDDT_BANDS) },
106
- },
107
100
  color: thresholdBandColor('score', PLDDT_BANDS),
108
101
  },
109
102
  ],
@@ -113,8 +106,8 @@ function addAlphaFoldConfidenceTrack({ session, uniprotId, confidenceUrl, }) {
113
106
  /**
114
107
  * Adds AlphaMissense pathogenicity scores track
115
108
  */
116
- function addAlphaMissenseTrack({ session, uniprotId, hasMultiWiggleDisplay, }) {
117
- session.addTrackConf({
109
+ function addAlphaMissenseTrack({ session, uniprotId, }) {
110
+ session.addSessionTrackConf({
118
111
  type: 'MultiQuantitativeTrack',
119
112
  trackId: `${uniprotId}-AlphaMissense-scores`,
120
113
  name: 'AlphaMissense scores',
@@ -125,28 +118,18 @@ function addAlphaMissenseTrack({ session, uniprotId, hasMultiWiggleDisplay, }) {
125
118
  uri: `https://alphafold.ebi.ac.uk/files/AF-${uniprotId}-F1-aa-substitutions.csv`,
126
119
  },
127
120
  },
128
- // v5 folded MultiLinearWiggleDisplay into LinearWiggleDisplay and warns on
129
- // a config naming the old type. v4's density renderer ramps from white to
130
- // posColor, so pathogenic reads red; v5 diverges at AlphaMissense's 0.5.
131
121
  displays: [
132
- hasMultiWiggleDisplay
133
- ? {
134
- type: 'MultiLinearWiggleDisplay',
135
- displayId: `${uniprotId}-AlphaMissense-scores-MultiLinearWiggleDisplay`,
136
- defaultRendering: 'multirowdensity',
137
- renderers: { MultiDensityRenderer: { posColor: '#d7191c' } },
138
- }
139
- : {
140
- type: 'LinearWiggleDisplay',
141
- displayId: `${uniprotId}-AlphaMissense-scores-LinearWiggleDisplay`,
142
- defaultRendering: 'density',
143
- color: {
144
- field: 'score',
145
- scale: 'linear',
146
- range: ['#2c7bb6', '#ffffff', '#d7191c'],
147
- domainMid: 0.5,
148
- },
122
+ {
123
+ type: 'LinearWiggleDisplay',
124
+ displayId: `${uniprotId}-AlphaMissense-scores-LinearWiggleDisplay`,
125
+ defaultRendering: 'density',
126
+ color: {
127
+ field: 'score',
128
+ scale: 'linear',
129
+ range: ['#2c7bb6', '#ffffff', '#d7191c'],
130
+ domainMid: 0.5,
149
131
  },
132
+ },
150
133
  ],
151
134
  });
152
135
  }
@@ -178,9 +161,6 @@ export async function addAllProteinTracks({ session, uniprotId, confidenceUrl, }
178
161
  addAlphaMissenseTrack({
179
162
  session,
180
163
  uniprotId,
181
- hasMultiWiggleDisplay: getEnv(session)
182
- .pluginManager.getDisplayElements()
183
- .some(d => d.name === 'MultiLinearWiggleDisplay'),
184
164
  });
185
165
  }
186
166
  }
@@ -1,21 +1,7 @@
1
1
  /**
2
- * A v4 wiggle renderer's `color` callback painting `field` by band. v4 hosts
3
- * read colour only from the renderer, and only a callback can place a cut
4
- * anywhere but 0 on v4.3.0, which drops a configured pivot on its way to the
5
- * worker. v4 also evaluates it once with no feature, and a throw there leaves
6
- * the track an error message, hence the guard.
7
- */
8
- export function jexlBandColor(field, bands) {
9
- const value = `get(feature,'${field}')`;
10
- const body = bands.reduceRight((rest, band) => band.upTo === Infinity
11
- ? `'${band.color}'`
12
- : `(${value}<=${band.upTo}?'${band.color}':${rest})`, "'#cccccc'");
13
- return `jexl:feature?${body}:'#cccccc'`;
14
- }
15
- /**
16
- * The same bands as the display-level threshold scale v5 hosts read. That
17
- * scale puts a value equal to a cut in the upper band, so each cut moves just
18
- * past its bound to keep the bound in its own band, as `bandColor` does.
2
+ * The display-level threshold scale puts a value equal to a cut in the upper
3
+ * band, so each cut moves just past its bound to keep the bound in its own
4
+ * band, as `bandColor` does.
19
5
  */
20
6
  export function thresholdBandColor(field, bands) {
21
7
  return {
@@ -1,12 +1,15 @@
1
- import { getContainingTrack, getSession } from '@jbrowse/core/util';
1
+ import { getContainingTrack, getSession, isGeneLikeType, } from '@jbrowse/core/util';
2
2
  import AddIcon from '@mui/icons-material/Add';
3
- import { extendPluggableStateModel } from '../extendStateModel';
4
- import { geneLikeRoot, isCodingFeature, isGeneLikeType } from './codingFeature';
3
+ import { isCodingFeature } from './codingFeature';
5
4
  import LaunchProteinViewDialog from './components/LaunchProteinViewDialog';
6
5
  function isDisplay(elt) {
7
6
  return elt.name === 'LinearBasicDisplay';
8
7
  }
9
- function canvasTarget(info, fetchFullFeature) {
8
+ export function resolveTarget(self) {
9
+ const info = self.contextMenuInfo;
10
+ if (!info) {
11
+ return undefined;
12
+ }
10
13
  const { item, subfeature, displayedRegionIndex } = info;
11
14
  const type = subfeature ? subfeature.type : item.type;
12
15
  // The parent gene, not the clicked isoform: the dialog picks the transcript
@@ -16,32 +19,10 @@ function canvasTarget(info, fetchFullFeature) {
16
19
  ? undefined
17
20
  : {
18
21
  type,
19
- fetchFeature: () => fetchFullFeature(parentId, displayedRegionIndex),
22
+ fetchFeature: () => self.fetchFullFeature(parentId, displayedRegionIndex),
20
23
  preferredTranscriptId: subfeature?.featureId,
21
24
  };
22
25
  }
23
- // The hit test only carries a type, so a canvas host learns whether the gene
24
- // codes for anything after the fetch; a legacy host has the whole feature and
25
- // can decline up front.
26
- function legacyTarget(feature) {
27
- const root = geneLikeRoot(feature);
28
- const type = root.get('type');
29
- return type === undefined || !isCodingFeature(root)
30
- ? undefined
31
- : {
32
- type,
33
- feature: root,
34
- preferredTranscriptId: root === feature ? undefined : feature.id(),
35
- };
36
- }
37
- export function resolveTarget(self) {
38
- const { contextMenuFeature, contextMenuInfo, fetchFullFeature } = self;
39
- return contextMenuInfo && fetchFullFeature
40
- ? canvasTarget(contextMenuInfo, fetchFullFeature)
41
- : contextMenuFeature
42
- ? legacyTarget(contextMenuFeature)
43
- : undefined;
44
- }
45
26
  function launchProteinView(self, target) {
46
27
  const track = getContainingTrack(self);
47
28
  const session = getSession(track);
@@ -52,41 +33,32 @@ function launchProteinView(self, target) {
52
33
  { model: track, handleClose, feature, preferredTranscriptId },
53
34
  ]);
54
35
  };
55
- if ('feature' in target) {
56
- openDialog(target.feature);
57
- }
58
- else {
59
- target
60
- .fetchFeature()
61
- .then(feature => {
62
- if (!feature) {
63
- session.notify('Could not load feature for protein view', 'warning');
64
- }
65
- else if (!isCodingFeature(feature)) {
66
- session.notify(`${feature.get('name') ?? feature.get('id') ?? 'This feature'} has no coding sequence, so there is no protein to show`, 'info');
67
- }
68
- else {
69
- openDialog(feature);
70
- }
71
- })
72
- .catch((e) => {
73
- console.error(e);
74
- session.notifyError(`${e}`, e);
75
- });
76
- }
36
+ target
37
+ .fetchFeature()
38
+ .then(feature => {
39
+ if (!feature) {
40
+ session.notify('Could not load feature for protein view', 'warning');
41
+ }
42
+ else if (!isCodingFeature(feature)) {
43
+ session.notify(`${feature.get('name') ?? feature.get('id') ?? 'This feature'} has no coding sequence, so there is no protein to show`, 'info');
44
+ }
45
+ else {
46
+ openDialog(feature);
47
+ }
48
+ })
49
+ .catch((e) => {
50
+ console.error(e);
51
+ session.notifyError(`${e}`, e);
52
+ });
77
53
  }
78
54
  function extendStateModel(stateModel) {
79
55
  return stateModel.views((self) => {
80
- // .call(self), not a bare call: the canvas display's own contextMenuItems
81
- // reads `this.isGeneLike`, so invoking it detached throws on undefined and
82
- // the ErrorBoundary around the menu swallows it -- the user right-clicks a
83
- // feature and gets no menu at all, not merely no protein item.
84
56
  const superContextMenuItems = self.contextMenuItems;
85
57
  return {
86
58
  contextMenuItems() {
87
59
  const target = resolveTarget(self);
88
60
  return [
89
- ...superContextMenuItems.call(self),
61
+ ...superContextMenuItems(),
90
62
  ...(target && isGeneLikeType(target.type)
91
63
  ? [
92
64
  {
@@ -106,7 +78,7 @@ function extendStateModel(stateModel) {
106
78
  export default function LaunchProteinViewF(pluginManager) {
107
79
  pluginManager.addToExtensionPoint('Core-extendPluggableElement', (elt) => {
108
80
  if (isDisplay(elt)) {
109
- extendPluggableStateModel(elt, extendStateModel);
81
+ elt.extendStateModel(extendStateModel);
110
82
  }
111
83
  return elt;
112
84
  });
@@ -1,4 +1,4 @@
1
- import { isSessionWithAddTracks } from './sessionWithAddTracks';
1
+ import { isSessionWithAddSessionTrack } from '@jbrowse/core/util';
2
2
  import { maybeLaunchSideBySide } from './sideBySide';
3
3
  import { getGeneDisplayName, getTranscriptDisplayName } from './util';
4
4
  import { proteinViewSnapshot } from '../../ProteinView/proteinViewSpec';
@@ -35,7 +35,7 @@ export function launch3DProteinView({ session, view, feature, selectedTranscript
35
35
  return proteinView;
36
36
  }
37
37
  // The 1D annotation view adds temporary tracks/assemblies, so it requires a
38
- // SessionWithAddTracks and a known uniprotId. Demanding both in the signature
38
+ // SessionWithAddSessionTrack and a known uniprotId. Demanding both in the signature
39
39
  // forces callers to narrow up front — there's no silent no-op when a wide
40
40
  // session or missing id slips through.
41
41
  async function launch1DProteinView({ session, view, feature, selectedTranscript, uniprotId, confidenceUrl, }) {
@@ -63,7 +63,9 @@ export const PROTEIN_LAUNCH_LABELS = {
63
63
  // single source of truth — an unavailable action is unrepresentable rather than
64
64
  // a menu item that silently no-ops.
65
65
  export function getConditionalProteinLaunches({ session, view, feature, selectedTranscript, uniprotId, confidenceUrl, }) {
66
- const addTracksSession = isSessionWithAddTracks(session) ? session : undefined;
66
+ const addTracksSession = isSessionWithAddSessionTrack(session)
67
+ ? session
68
+ : undefined;
67
69
  return {
68
70
  launch1D: addTracksSession && uniprotId
69
71
  ? () => launch1DProteinView({
@@ -21,39 +21,16 @@ let warnedPartial = false;
21
21
  function isSessionWithWorkspaces(session) {
22
22
  const canEnable = hasAction(session, 'setUseWorkspaces');
23
23
  const canPlace = hasAction(session, 'setPendingMove');
24
- // Missing BOTH is an embedded session: it has no workspaces, there is nothing
25
- // to ask for, and silence is the right answer.
26
- //
27
- // Missing ONE is a host that has workspaces but places views some other way —
28
- // and silence there is how this broke before. jbrowse-web folded
29
- // `setPendingMove` into its layout `init`, this guard went false, and the
30
- // plugin simply stopped asking for the split: no error, no missing feature,
31
- // just two views quietly stacking. Nobody noticed for weeks. Feature
32
- // detection cannot ask the host to announce a change, but it can tell "not
33
- // supported here" from "supported, and gone".
34
- //
35
- // Two very different hosts produce this one shape, and nothing on the session
36
- // tells them apart, so the message carries both rather than the guess:
37
- //
38
- // - Releases through v4.3.0, where placement is `setPendingMoveToSplitRight`,
39
- // a module function in @jbrowse/app-core's DockviewContext, not a session
40
- // action. Nothing is wrong and nothing needs fixing; this plugin targets v5+
41
- // and does not reach for v4's door. Measured 2026-08-17: v4.3.0 and latest
42
- // stack, main splits.
43
- // - A newer host that moved the action out from under us, which is the
44
- // regression this warning exists to catch.
45
- //
46
- // Do not quiet the first case by sniffing the version. The alarm is only worth
47
- // having if it fires on a shape it cannot explain, and these two shapes are
48
- // identical.
24
+ // Missing BOTH is an embedded session, with no workspaces to ask for. Missing
25
+ // ONE means the host moved the action out from under us: jbrowse-web once
26
+ // folded `setPendingMove` into its layout `init`, and the views quietly
27
+ // stacked for weeks, so that shape warns.
49
28
  if (canEnable !== canPlace && !warnedPartial) {
50
29
  warnedPartial = true;
51
30
  console.warn(`jbrowse-plugin-protein3d: this session supports workspaces but not ` +
52
31
  `${canPlace ? 'setUseWorkspaces' : 'setPendingMove'}, so the ` +
53
- `side-by-side launch was skipped and the views will stack. Expected on ` +
54
- `releases through v4.3.0, which place views through @jbrowse/app-core ` +
55
- `instead; on a newer host it means the session API moved and the plugin ` +
56
- `needs updating to match.`);
32
+ `side-by-side launch was skipped and the views will stack. The session ` +
33
+ `API moved and the plugin needs updating to match.`);
57
34
  }
58
35
  return canEnable && canPlace;
59
36
  }
@@ -16,11 +16,8 @@ export async function fetchRegionSequence({ session, assemblyName, refName, star
16
16
  if (!assembly) {
17
17
  throw new Error('assembly not found');
18
18
  }
19
- const sessionId = 'getSequence';
20
- // a named object keeps sessionId, which v4 hosts read from the args
21
- const args = {
19
+ const [feat] = await rpcManager.call('getSequence', 'CoreGetFeatures', {
22
20
  adapterConfig: getConf(assembly, ['sequence', 'adapter']),
23
- sessionId,
24
21
  regions: [
25
22
  {
26
23
  start,
@@ -29,10 +26,9 @@ export async function fetchRegionSequence({ session, assemblyName, refName, star
29
26
  assemblyName: assembly.name,
30
27
  },
31
28
  ],
32
- };
33
- const [feat] = await rpcManager.call(sessionId, 'CoreGetFeatures', args);
29
+ });
34
30
  const seq = feat?.get('seq');
35
- return { seq, assemblyGeneticCodeId: assemblyGeneticCode(assembly, refName) };
31
+ return { seq, assemblyGeneticCodeId: assembly.getGeneticCodeId(refName) };
36
32
  }
37
33
  export async function fetchProteinSeq({ feature, session, assemblyName, }) {
38
34
  const { seq, assemblyGeneticCodeId } = await fetchRegionSequence({
@@ -85,8 +81,3 @@ export async function fetchTranscriptProteinSeqs({ transcripts, session, assembl
85
81
  fetchSpan: span => fetchRegionSequence({ session, assemblyName, ...span }),
86
82
  });
87
83
  }
88
- // v5 hosts only; a v4 assembly has neither the method nor the config slot
89
- function assemblyGeneticCode(assembly, refName) {
90
- const { getGeneticCodeId } = assembly;
91
- return getGeneticCodeId?.call(assembly, refName);
92
- }
@@ -1,5 +1,6 @@
1
+ import { isGeneLikeType } from '@jbrowse/core/util';
1
2
  import { isRecognizedDatabaseId, matchDbIdPattern } from 'p2s_mapper';
2
- import { codingTranscripts, isGeneLikeType } from '../codingFeature';
3
+ import { codingTranscripts } from '../codingFeature';
3
4
  /**
4
5
  * Pull an NCBI taxon id out of reference-sequence-track metadata. jb2hubs
5
6
  * assemblies expose it differently by source: UCSC golden-path spreads it flat
@@ -14,10 +14,6 @@ export function launchViewSnapshot({ alignmentAlgorithm, colorScheme, ...setting
14
14
  });
15
15
  }
16
16
  export default function LaunchProteinViewExtensionPointF(pluginManager) {
17
- // v4 hosts declare `init`; v5 reads the settings off the view object and
18
- // warns about the nesting
19
- const lgvTakesInit = () => 'init' in
20
- pluginManager.getViewType('LinearGenomeView').stateModel.properties;
21
17
  pluginManager.addToExtensionPoint('LaunchView-ProteinView',
22
18
  // A LaunchView point is a transformer — the chain hands what each callback
23
19
  // returns to the next — and JBrowse now warns when one returns undefined
@@ -81,9 +77,10 @@ export default function LaunchProteinViewExtensionPointF(pluginManager) {
81
77
  const ownsConnectedView = !connectedViewId && !!connectedView;
82
78
  const resolvedConnectedViewId = connectedViewId ??
83
79
  (connectedView
84
- ? session.addView('LinearGenomeView', lgvTakesInit()
85
- ? { type: 'LinearGenomeView', init: connectedView }
86
- : { ...connectedView, type: 'LinearGenomeView' }).id
80
+ ? session.addView('LinearGenomeView', {
81
+ ...connectedView,
82
+ type: 'LinearGenomeView',
83
+ }).id
87
84
  : undefined);
88
85
  const structures = requested.map((s, i) => ({
89
86
  url: urls[i],
@@ -10,22 +10,6 @@ function getTrackId(track) {
10
10
  const { trackId } = track;
11
11
  return typeof trackId === 'string' ? trackId : undefined;
12
12
  }
13
- /**
14
- * Track config for a trackId, reading whichever lookup this host has. Hosts are
15
- * not all current: a hub config lives at one permanent url that desktop installs
16
- * and published links keep naming, so this runs on builds years apart.
17
- * `getTrackById` landed 2026-07 and `getTracksById` 2026-01, while a session's
18
- * own `tracks` array has been there throughout — measured floor for the whole
19
- * short-form launch was v4.2.0 when this only called `getTracksById()`, purely
20
- * because of that one call. Probe it with
21
- * `pnpm host-compat -- --floor <version>`.
22
- */
23
- function findTrackConf(session, trackId) {
24
- const host = session;
25
- return (host.getTrackById?.(trackId) ??
26
- host.getTracksById?.()[trackId] ??
27
- host.tracks?.find(t => readConfObject(t, 'trackId') === trackId));
28
- }
29
13
  // `transcript_id` because Ensembl's GFF3 prefixes the ID (`transcript:ENST…`)
30
14
  function transcriptMatches(transcript, transcriptId) {
31
15
  const target = stripTrailingVersion(transcriptId);
@@ -44,15 +28,11 @@ function isFeatureTrack(trackConf) {
44
28
  return readConfObject(trackConf, 'type') === 'FeatureTrack';
45
29
  }
46
30
  async function findTranscript({ session, trackConfs, region, transcriptId, }) {
47
- const sessionId = 'getFeatures';
48
31
  for (const trackConf of trackConfs) {
49
- // a named object keeps sessionId, which v4 hosts read from the args
50
- const args = {
32
+ const feats = await session.rpcManager.call('getFeatures', 'CoreGetFeatures', {
51
33
  adapterConfig: readConfObject(trackConf, 'adapter'),
52
- sessionId,
53
34
  regions: [region],
54
- };
55
- const feats = await session.rpcManager.call(sessionId, 'CoreGetFeatures', args);
35
+ });
56
36
  for (const feat of feats) {
57
37
  const hit = codingTranscripts(feat).find(t => transcriptMatches(t, transcriptId));
58
38
  if (hit) {
@@ -99,7 +79,7 @@ export async function resolveShortLaunch({ session, transcriptId, connectedView,
99
79
  const transcript = await findTranscript({
100
80
  session,
101
81
  trackConfs: trackIds.flatMap(trackId => {
102
- const conf = findTrackConf(session, trackId);
82
+ const conf = session.getTrackById(trackId);
103
83
  return conf && isFeatureTrack(conf) ? [conf] : [];
104
84
  }),
105
85
  region,
@@ -1,5 +1,4 @@
1
1
  import { types } from '@jbrowse/mobx-state-tree';
2
- import { extendPluggableStateModel } from '../extendStateModel';
3
2
  import { linkageGenomeMapping } from './linkage';
4
3
  export { findProteinLinkedView, genomeHighlightsForProteinPosition, getProteinLinkage, getProteinLinkageMapping, hovered1DProteinPosition, } from './linkage';
5
4
  function isLinearGenomeView(elt) {
@@ -28,7 +27,7 @@ export function withProteinLinkage(stateModel) {
28
27
  export default function Protein1DLinkageF(pluginManager) {
29
28
  pluginManager.addToExtensionPoint('Core-extendPluggableElement', (elt) => {
30
29
  if (isLinearGenomeView(elt)) {
31
- extendPluggableStateModel(elt, withProteinLinkage);
30
+ elt.extendStateModel(withProteinLinkage);
32
31
  }
33
32
  return elt;
34
33
  });
@@ -8,11 +8,7 @@ const ALIGNMENT_RPC_SESSION = 'protein3d-alignment';
8
8
  */
9
9
  export async function alignOffThread({ rpcManager, name, args, inPlace, current = () => true, }) {
10
10
  try {
11
- // v4 hosts read sessionId from the args as well as the call
12
- return await rpcManager.call(ALIGNMENT_RPC_SESSION, name, {
13
- ...args,
14
- sessionId: ALIGNMENT_RPC_SESSION,
15
- });
11
+ return await rpcManager.call(ALIGNMENT_RPC_SESSION, name, args);
16
12
  }
17
13
  catch (e) {
18
14
  if (!current()) {
@@ -29,7 +29,7 @@ const ColorSchemeMenu = observer(function ColorSchemeMenu({ model, }) {
29
29
  setAnchorEl(event.currentTarget);
30
30
  } },
31
31
  React.createElement(PaletteIcon, { fontSize: "small" }))),
32
- React.createElement(Menu, { keepMounted: true, anchorEl: anchorEl, open: Boolean(anchorEl), onClose: () => {
32
+ React.createElement(Menu, { anchorEl: anchorEl, open: Boolean(anchorEl), onClose: () => {
33
33
  setAnchorEl(null);
34
34
  } }, COLOR_SCHEMES.map(scheme => (React.createElement(MenuItem, { key: scheme.value, dense: true, selected: scheme.value === model.colorScheme, onClick: () => {
35
35
  model.setColorScheme(scheme.value);
@@ -55,7 +55,7 @@ const DisplaySettingsMenu = observer(function DisplaySettingsMenu({ model, }) {
55
55
  setAnchorEl(event.currentTarget);
56
56
  } },
57
57
  React.createElement(TuneIcon, { fontSize: "small" }))),
58
- React.createElement(Menu, { keepMounted: true, anchorEl: anchorEl, open: Boolean(anchorEl), onClose: () => {
58
+ React.createElement(Menu, { anchorEl: anchorEl, open: Boolean(anchorEl), onClose: () => {
59
59
  setAnchorEl(null);
60
60
  } },
61
61
  model.displayToggles.map(toggle => (React.createElement(ToggleMenuItem, { key: toggle.label, checked: toggle.checked, label: toggle.label, onToggle: toggle.toggle }))),
@@ -1,2 +1,2 @@
1
- import{a as s}from"./chunk-P5HUHQAD.js";import"./chunk-X5RLRQSB.js";import"./chunk-533QNNGN.js";import{a as l,b as a}from"./chunk-J236G3A2.js";import{a as r}from"./chunk-GVEKA5YG.js";import{c as t}from"./chunk-KVMUXFPB.js";var e=t(r()),n=t(l()),o=t(a());var h="https://github.com/GMOD/jbrowse-plugin-protein3d/issues";function p({handleClose:i}){return e.default.createElement(n.Dialog,{open:!0,maxWidth:"lg",onClose:i,title:"Help"},e.default.createElement(o.DialogContent,null,e.default.createElement(o.Typography,{sx:{mb:2}},"Each tab finds a structure a different way. All of them end in the same place: the plugin aligns the structure's residues to the protein sequence it translates from the transcript you pick, and that alignment maps genome coordinates onto positions in the 3D view."),e.default.createElement(o.Typography,{component:"div"},e.default.createElement("ul",null,e.default.createElement("li",null,e.default.createElement("b",null,"AlphaFoldDB search")," resolves the feature to a UniProt accession \u2014 from its own identifiers, or one you type \u2014 and opens AlphaFold's predicted model for it."),e.default.createElement("li",null,e.default.createElement("b",null,"PDB search")," lists the experimental structures PDBe maps to that accession, ranked on coverage and resolution. A crystal is usually one domain, often with binding partners, so the view picks the chain the transcript belongs to once the structure loads."),e.default.createElement("li",null,e.default.createElement("b",null,"Foldseek search")," sends the protein sequence to the foldseek.com servers and lists structures similar in shape, including ones with little sequence similarity."),e.default.createElement("li",null,e.default.createElement("b",null,"File or URL")," opens a structure you already have \u2014 the output of ColabFold or another modelling tool, or any PDB/mmCIF file reachable by URL."))),e.default.createElement(o.Typography,{sx:{mb:2}},"The isoform list marks which transcripts match the structure's residues exactly, and counts identical residues for the rest. You do not need an exact match; the alignment absorbs the differences between the two representations."),e.default.createElement(o.Typography,null,"If a gene will not resolve, or something looks wrong, please open an issue at"," ",e.default.createElement(s,{href:h},"github.com/GMOD/jbrowse-plugin-protein3d"),".")),e.default.createElement(o.Divider,null),e.default.createElement(o.DialogActions,null,e.default.createElement(o.Button,{onClick:i,color:"primary"},"Close")))}export{p as default};
2
- //# sourceMappingURL=HelpDialog-KAG73BNB.js.map
1
+ import{a as s}from"./chunk-P5HUHQAD.js";import"./chunk-X5RLRQSB.js";import"./chunk-533QNNGN.js";import{a as r,b as a}from"./chunk-J236G3A2.js";import{a as l}from"./chunk-GVEKA5YG.js";import{c as t}from"./chunk-KVMUXFPB.js";var e=t(l()),n=t(r()),o=t(a());var h="https://github.com/GMOD/jbrowse-plugin-protein3d/issues";function p({handleClose:i}){return e.default.createElement(n.Dialog,{open:!0,maxWidth:"lg",onClose:i,title:"Help"},e.default.createElement(o.DialogContent,null,e.default.createElement(o.Typography,{sx:{mb:2}},"Each tab finds a structure a different way. All of them end in the same place: the plugin aligns the structure's residues to the protein sequence it translates from the transcript you pick, and that alignment maps genome coordinates onto positions in the 3D view."),e.default.createElement(o.Typography,{component:"div"},e.default.createElement("ul",null,e.default.createElement("li",null,e.default.createElement("b",null,"AlphaFoldDB search")," resolves the feature to a UniProt accession \u2014 from its own identifiers, or one you type \u2014 and opens AlphaFold's predicted model for it."),e.default.createElement("li",null,e.default.createElement("b",null,"PDB search")," lists the experimental structures PDBe maps to that accession, ranked on coverage and resolution. A crystal is usually one domain, often with binding partners, so the view picks the chain the transcript belongs to once the structure loads."),e.default.createElement("li",null,e.default.createElement("b",null,"Foldseek search")," sends the protein sequence to the foldseek.com servers and lists structures similar in shape, including ones with little sequence similarity."),e.default.createElement("li",null,e.default.createElement("b",null,"File or URL")," opens a structure you already have \u2014 the output of ColabFold or another modelling tool, or any PDB/mmCIF file reachable by URL."))),e.default.createElement(o.Typography,{sx:{mb:2}},"The isoform list marks which transcripts match the structure's residues exactly, and counts identical residues for the rest. You do not need an exact match; the alignment absorbs the differences between the two representations."),e.default.createElement(o.Typography,null,"If a gene will not resolve, or something looks wrong, please open an issue at"," ",e.default.createElement(s,{href:h},"github.com/GMOD/jbrowse-plugin-protein3d"),".")),e.default.createElement(o.Divider,null),e.default.createElement(o.DialogActions,null,e.default.createElement(o.Button,{onClick:i,color:"primary"},"Close")))}export{p as default};
2
+ //# sourceMappingURL=HelpDialog-I4UK7TQB.js.map
@@ -1,2 +1,2 @@
1
- import{a as s,b as h}from"./chunk-J236G3A2.js";import{a}from"./chunk-GVEKA5YG.js";import{c as i}from"./chunk-KVMUXFPB.js";var e=i(a()),n=i(s()),t=i(h());function r({children:o}){return e.default.createElement(t.Typography,{style:{margin:4,marginBottom:12}},o)}function p({handleClose:o}){return e.default.createElement(n.Dialog,{open:!0,maxWidth:"lg",onClose:o,title:"Protein alignment"},e.default.createElement(t.DialogContent,null,e.default.createElement(r,null,"This panel shows the computed pairwise alignment of the reference genome sequence to the structure sequence. The structure file (PDB file, mmCIF file, etc) has a stored representation of the e.g. amino acid sequence but the sequence in the structure file can differ from the sequence from the gene on the genome browser"),e.default.createElement(r,null,"In order to resolve this, we align the two sequences in the browser (Smith-Waterman by default, or Needleman-Wunsch, both with BLOSUM62 and EMBOSS-style gap penalties) to get a pairwise alignment of the genome's representation of the protein and the structure file's representation of the protein. Positions that fall in a gap are unmapped and will not highlight. Each structure's row in the header reports the identity and how much of the transcript it covers; an alignment always comes back with something, so a low-similarity warning there means the mapped positions may be chance rather than homology, and the chain picker or a different isoform is the first thing to check."),e.default.createElement(r,null,"The GENOME row is the transcript's translation and the STRUCT row is the structure file's own sequence, shaded amber where it differs from the transcript by a similar amino acid and red where it differs by a dissimilar one. The ruler under them counts residues the way the structure's authors numbered them, which for a PDB entry is the numbering papers and UniProt cite (p53's R248 is residue 248 in 1TUP even though the crystallised fragment starts at residue 94). Click a residue to select it and jump the genome view to its codon; hover one to light it in the 3D view."),e.default.createElement(r,null,"If you need a 100% fidelity protein, you can do a folding with e.g. AlphaFold to make sure the structure you are using matches exactly the sequence of the transcript")),e.default.createElement(t.DialogActions,null,e.default.createElement(t.Button,{onClick:()=>{o()},variant:"contained",color:"primary"},"Close")))}export{p as default};
2
- //# sourceMappingURL=ProteinAlignmentHelpDialog-L2GH5YTM.js.map
1
+ import{a,b as h}from"./chunk-J236G3A2.js";import{a as s}from"./chunk-GVEKA5YG.js";import{c as i}from"./chunk-KVMUXFPB.js";var e=i(s()),n=i(a()),t=i(h());function r({children:o}){return e.default.createElement(t.Typography,{style:{margin:4,marginBottom:12}},o)}function p({handleClose:o}){return e.default.createElement(n.Dialog,{open:!0,maxWidth:"lg",onClose:o,title:"Protein alignment"},e.default.createElement(t.DialogContent,null,e.default.createElement(r,null,"This panel shows the computed pairwise alignment of the reference genome sequence to the structure sequence. The structure file (PDB file, mmCIF file, etc) has a stored representation of the e.g. amino acid sequence but the sequence in the structure file can differ from the sequence from the gene on the genome browser"),e.default.createElement(r,null,"In order to resolve this, we align the two sequences in the browser (Smith-Waterman by default, or Needleman-Wunsch, both with BLOSUM62 and EMBOSS-style gap penalties) to get a pairwise alignment of the genome's representation of the protein and the structure file's representation of the protein. Positions that fall in a gap are unmapped and will not highlight. Each structure's row in the header reports the identity and how much of the transcript it covers; an alignment always comes back with something, so a low-similarity warning there means the mapped positions may be chance rather than homology, and the chain picker or a different isoform is the first thing to check."),e.default.createElement(r,null,"The GENOME row is the transcript's translation and the STRUCT row is the structure file's own sequence, shaded amber where it differs from the transcript by a similar amino acid and red where it differs by a dissimilar one. The ruler under them counts residues the way the structure's authors numbered them, which for a PDB entry is the numbering papers and UniProt cite (p53's R248 is residue 248 in 1TUP even though the crystallised fragment starts at residue 94). Click a residue to select it and jump the genome view to its codon; hover one to light it in the 3D view."),e.default.createElement(r,null,"If you need a 100% fidelity protein, you can do a folding with e.g. AlphaFold to make sure the structure you are using matches exactly the sequence of the transcript")),e.default.createElement(t.DialogActions,null,e.default.createElement(t.Button,{onClick:()=>{o()},variant:"contained",color:"primary"},"Close")))}export{p as default};
2
+ //# sourceMappingURL=ProteinAlignmentHelpDialog-6VFZFN6X.js.map