jbrowse-plugin-protein3d 0.8.2 → 0.8.4

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 (32) hide show
  1. package/dist/LaunchProteinView/components/AlignmentSettingsButton.js +2 -2
  2. package/dist/LaunchProteinView/components/FoldseekActionMenu.js +4 -4
  3. package/dist/LaunchProteinView/components/LaunchOptionsDialog.js +2 -2
  4. package/dist/LaunchProteinView/components/ProteinViewActions.js +5 -5
  5. package/dist/LaunchProteinView/components/StructureSourcePicker.js +1 -1
  6. package/dist/LaunchProteinView/components/UserProvidedStructure.js +1 -1
  7. package/dist/LaunchProteinView/index.js +5 -1
  8. package/dist/LaunchProteinView/utils/launchViewUtils.d.ts +6 -0
  9. package/dist/LaunchProteinView/utils/launchViewUtils.js +17 -0
  10. package/dist/LaunchProteinView/utils/sideBySide.js +26 -9
  11. package/dist/ProteinView/components/AddStructureDialog.js +3 -3
  12. package/dist/ProteinView/components/ManualAlignmentDialog.js +2 -2
  13. package/dist/jbrowse-plugin-protein3d.umd.production.min.js +14 -14
  14. package/dist/jbrowse-plugin-protein3d.umd.production.min.js.map +4 -4
  15. package/dist/{molstar-chunk-UJIWPKE3.js → molstar-chunk-JDIZPSL3.js} +1 -1
  16. package/dist/{molstar-chunk-UJIWPKE3.js.map → molstar-chunk-JDIZPSL3.js.map} +1 -1
  17. package/dist/version.d.ts +1 -1
  18. package/dist/version.js +1 -1
  19. package/package.json +3 -2
  20. package/src/LaunchProteinView/components/AlignmentSettingsButton.tsx +2 -2
  21. package/src/LaunchProteinView/components/FoldseekActionMenu.tsx +17 -5
  22. package/src/LaunchProteinView/components/LaunchOptionsDialog.tsx +15 -2
  23. package/src/LaunchProteinView/components/ProteinViewActions.tsx +5 -4
  24. package/src/LaunchProteinView/components/StructureSourcePicker.tsx +1 -1
  25. package/src/LaunchProteinView/components/UserProvidedStructure.tsx +1 -1
  26. package/src/LaunchProteinView/index.ts +5 -1
  27. package/src/LaunchProteinView/utils/launchViewUtils.ts +18 -0
  28. package/src/LaunchProteinView/utils/sideBySide.test.ts +6 -0
  29. package/src/LaunchProteinView/utils/sideBySide.ts +26 -9
  30. package/src/ProteinView/components/AddStructureDialog.tsx +3 -3
  31. package/src/ProteinView/components/ManualAlignmentDialog.tsx +2 -2
  32. package/src/version.ts +1 -1
@@ -42,7 +42,7 @@ export default function AlignmentSettingsButton({ value, onChange, onManualAlign
42
42
  React.createElement(IconButton, { onClick: handleOpen, size: "small", title: "Alignment settings" },
43
43
  React.createElement(SettingsIcon, null)),
44
44
  React.createElement(Dialog, { open: open, onClose: handleCancel, maxWidth: "sm", fullWidth: true },
45
- React.createElement(DialogTitle, null, "Alignment Settings"),
45
+ React.createElement(DialogTitle, null, "Alignment settings"),
46
46
  React.createElement(DialogContent, null,
47
47
  React.createElement(Tabs, { value: tabValue, onChange: (_, val) => {
48
48
  setTabValue(val);
@@ -71,5 +71,5 @@ b MKAAYLSMFGKEDHKPFGD`, value: manualAlignment, onChange: e => {
71
71
  parseError ? (React.createElement(Typography, { color: "error", variant: "body2", sx: { mt: 1 } }, parseError)) : null))),
72
72
  React.createElement(DialogActions, null,
73
73
  React.createElement(Button, { onClick: handleCancel }, "Cancel"),
74
- React.createElement(Button, { onClick: handleSave, variant: "contained", color: "primary", disabled: tabValue === 1 && !manualAlignment.trim() }, tabValue === 0 ? 'Save' : 'Apply Alignment')))));
74
+ React.createElement(Button, { onClick: handleSave, variant: "contained", color: "primary", disabled: tabValue === 1 && !manualAlignment.trim() }, tabValue === 0 ? 'Save' : 'Apply alignment')))));
75
75
  }
@@ -3,7 +3,7 @@ import { ErrorMessage } from '@jbrowse/core/ui';
3
3
  import { Button, Menu, MenuItem } from '@mui/material';
4
4
  import { useSafeLaunch } from '../hooks/useSafeLaunch';
5
5
  import { caCoordsToPdb, hasValidCaCoords } from '../utils/caCoordsToPdb';
6
- import { getConditionalProteinLaunches, launch3DProteinView, } from '../utils/launchViewUtils';
6
+ import { PROTEIN_LAUNCH_LABELS, getConditionalProteinLaunches, launch3DProteinView, } from '../utils/launchViewUtils';
7
7
  import { getConfidenceUrlFromTarget, getUniprotIdFromAlphaFoldTarget, } from '../utils/structureUrls';
8
8
  export default function FoldseekActionMenu({ hit, session, view, feature, selectedTranscript, userProvidedTranscriptSequence, onClose, }) {
9
9
  const [anchorEl, setAnchorEl] = useState(null);
@@ -41,7 +41,7 @@ export default function FoldseekActionMenu({ hit, session, view, feature, select
41
41
  launchError ? React.createElement(ErrorMessage, { error: launchError }) : null,
42
42
  React.createElement(Button, { size: "small", variant: "outlined", onClick: handleClick }, "Load"),
43
43
  React.createElement(Menu, { anchorEl: anchorEl, open: open, onClose: handleMenuClose },
44
- React.createElement(MenuItem, { onClick: handleLaunch3D }, "Launch 3D protein view"),
45
- launch1D ? (React.createElement(MenuItem, { onClick: runLaunch(launch1D) }, "Launch 1D protein annotation view")) : null,
46
- launchMsa ? (React.createElement(MenuItem, { onClick: runLaunch(launchMsa) }, "Launch MSA view (AlphaFoldDB a3m)")) : null)));
44
+ React.createElement(MenuItem, { "data-testid": "protein-launch-option-3d", onClick: handleLaunch3D }, PROTEIN_LAUNCH_LABELS['3d']),
45
+ launch1D ? (React.createElement(MenuItem, { "data-testid": "protein-launch-option-1d", onClick: runLaunch(launch1D) }, PROTEIN_LAUNCH_LABELS['1d'])) : null,
46
+ launchMsa ? (React.createElement(MenuItem, { "data-testid": "protein-launch-option-msa", onClick: runLaunch(launchMsa) }, PROTEIN_LAUNCH_LABELS.msa)) : null)));
47
47
  }
@@ -1,10 +1,10 @@
1
1
  import React from 'react';
2
2
  import { Dialog, DialogContent, DialogTitle, MenuItem, MenuList, Typography, } from '@mui/material';
3
3
  export default function LaunchOptionsDialog({ open, onClose, options, }) {
4
- return (React.createElement(Dialog, { open: open, onClose: onClose },
4
+ return (React.createElement(Dialog, { open: open, onClose: onClose, "data-testid": "protein-launch-options-dialog" },
5
5
  React.createElement(DialogTitle, null, "Launch options"),
6
6
  React.createElement(DialogContent, null,
7
- React.createElement(MenuList, null, options.map(opt => (React.createElement(MenuItem, { key: opt.key, onClick: opt.onClick },
7
+ React.createElement(MenuList, null, options.map(opt => (React.createElement(MenuItem, { key: opt.key, "data-testid": `protein-launch-option-${opt.key}`, onClick: opt.onClick },
8
8
  React.createElement("div", null,
9
9
  React.createElement(Typography, { variant: "body1" }, opt.title),
10
10
  React.createElement(Typography, { variant: "body2", color: "text.secondary" }, opt.description)))))))));
@@ -8,7 +8,7 @@ import LaunchSettingsDialog from './LaunchSettingsDialog';
8
8
  import SequenceMismatchNotice from './SequenceMismatchNotice';
9
9
  import { useSafeLaunch } from '../hooks/useSafeLaunch';
10
10
  import { getLaunchMissingReasons } from '../utils/launchHelpers';
11
- import { getConditionalProteinLaunches, launch3DProteinView, launch3DProteinViewWithMsa, } from '../utils/launchViewUtils';
11
+ import { PROTEIN_LAUNCH_LABELS, getConditionalProteinLaunches, launch3DProteinView, launch3DProteinViewWithMsa, } from '../utils/launchViewUtils';
12
12
  export default function ProteinViewActions({ handleClose, uniprotId, userSelectedProteinSequence, selectedTranscript, url, confidenceUrl, feature, view, session, alignmentAlgorithm, onAlignmentAlgorithmChange, sequencesMatch, isLoading, error, }) {
13
13
  const [dialogOpen, setDialogOpen] = useState(false);
14
14
  const [settingsOpen, setSettingsOpen] = useState(false);
@@ -51,7 +51,7 @@ export default function ProteinViewActions({ handleClose, uniprotId, userSelecte
51
51
  const launchOptions = [
52
52
  {
53
53
  key: '3d',
54
- title: 'Launch 3D protein structure view',
54
+ title: PROTEIN_LAUNCH_LABELS['3d'],
55
55
  description: 'View protein structure with genome-to-structure coordinate mapping',
56
56
  onClick: handleLaunch3DView,
57
57
  },
@@ -59,7 +59,7 @@ export default function ProteinViewActions({ handleClose, uniprotId, userSelecte
59
59
  ? [
60
60
  {
61
61
  key: '1d',
62
- title: 'Launch 1D protein annotation view',
62
+ title: PROTEIN_LAUNCH_LABELS['1d'],
63
63
  description: 'View protein features and annotations as a linear track',
64
64
  onClick: runLaunch(launch1D),
65
65
  },
@@ -69,13 +69,13 @@ export default function ProteinViewActions({ handleClose, uniprotId, userSelecte
69
69
  ? [
70
70
  {
71
71
  key: 'msa',
72
- title: 'Launch MSA view',
72
+ title: PROTEIN_LAUNCH_LABELS.msa,
73
73
  description: 'View AlphaFold a3m multiple sequence alignment',
74
74
  onClick: runLaunch(launchMsa),
75
75
  },
76
76
  {
77
77
  key: '3d-msa',
78
- title: 'Launch 3D structure + MSA view',
78
+ title: PROTEIN_LAUNCH_LABELS['3d-msa'],
79
79
  description: 'Launch both views with AlphaFold a3m MSA',
80
80
  onClick: runLaunch(() => launch3DProteinViewWithMsa(launch3DParams)),
81
81
  },
@@ -22,7 +22,7 @@ export default function StructureSourcePicker({ choice, setChoice, structureURL,
22
22
  choice === 'file' ? (React.createElement("div", { style: { paddingTop: 20 } },
23
23
  React.createElement(Typography, null, "Open a PDB/mmCIF/etc. file from your local drive"),
24
24
  React.createElement(Button, { variant: "outlined", component: "label" },
25
- "Choose File",
25
+ "Choose file",
26
26
  React.createElement("input", { type: "file", hidden: true, onChange: ({ target }) => {
27
27
  const f = target.files?.[0];
28
28
  if (f) {
@@ -79,6 +79,6 @@ const UserProvidedStructure = observer(function UserProvidedStructure({ feature,
79
79
  } }, "Cancel"),
80
80
  React.createElement(Button, { variant: "contained", color: "primary", disabled: !canLaunch, onClick: () => {
81
81
  handleLaunch();
82
- } }, "Launch 3-D protein structure view"))));
82
+ } }, "Launch 3D protein structure view"))));
83
83
  });
84
84
  export default UserProvidedStructure;
@@ -61,12 +61,16 @@ function launchProteinView(self, target) {
61
61
  }
62
62
  function extendStateModel(stateModel) {
63
63
  return stateModel.views((self) => {
64
+ // .call(self), not a bare call: the canvas display's own contextMenuItems
65
+ // reads `this.isGeneLike`, so invoking it detached throws on undefined and
66
+ // the ErrorBoundary around the menu swallows it -- the user right-clicks a
67
+ // feature and gets no menu at all, not merely no protein item.
64
68
  const superContextMenuItems = self.contextMenuItems;
65
69
  return {
66
70
  contextMenuItems() {
67
71
  const target = resolveTarget(self);
68
72
  return [
69
- ...superContextMenuItems(),
73
+ ...superContextMenuItems.call(self),
70
74
  ...(target && PROTEIN_FEATURE_TYPES.includes(target.type)
71
75
  ? [
72
76
  {
@@ -25,6 +25,12 @@ export declare function launch3DProteinView({ session, view, feature, selectedTr
25
25
  connectedMsaViewId?: string;
26
26
  sideBySide?: boolean;
27
27
  }): import("@jbrowse/core/util").AbstractViewModel;
28
+ export declare const PROTEIN_LAUNCH_LABELS: {
29
+ readonly '3d': "Launch 3D protein structure view";
30
+ readonly '1d': "Launch 1D protein annotation view";
31
+ readonly msa: "Launch MSA view (AlphaFold a3m)";
32
+ readonly '3d-msa': "Launch 3D structure + MSA view";
33
+ };
28
34
  export declare function getConditionalProteinLaunches({ session, view, feature, selectedTranscript, uniprotId, confidenceUrl, }: LaunchViewParams & {
29
35
  confidenceUrl?: string;
30
36
  }): {
@@ -77,6 +77,23 @@ function launchMsaView({ session, view, feature, selectedTranscript, uniprotId,
77
77
  function hasMsaViewPlugin() {
78
78
  return window.JBrowsePluginMsaView !== undefined;
79
79
  }
80
+ // What the launches below are CALLED, shared for the same reason their
81
+ // availability is: the AlphaFold and Foldseek menus offer the same three
82
+ // actions, and had drifted to different names for two of them ("Launch 3D
83
+ // protein structure view" vs "Launch 3D protein view", and an MSA item that
84
+ // named its source on one menu and not the other). Two names for one action
85
+ // reads as two actions.
86
+ //
87
+ // The MSA item says "(AlphaFold a3m)" rather than a bare "Launch MSA view"
88
+ // because msaview contributes a gene right-click item by that exact name, and
89
+ // the two build different things: msaview's is one row per species from NCBI's
90
+ // orthologs, this one is the deep unlabelled alignment AlphaFold folded from.
91
+ export const PROTEIN_LAUNCH_LABELS = {
92
+ '3d': 'Launch 3D protein structure view',
93
+ '1d': 'Launch 1D protein annotation view',
94
+ msa: 'Launch MSA view (AlphaFold a3m)',
95
+ '3d-msa': 'Launch 3D structure + MSA view',
96
+ };
80
97
  // The 1D-annotation and MSA launches share identical availability rules across
81
98
  // the AlphaFold and Foldseek launch menus: the 1D view needs an add-tracks
82
99
  // session and a uniprotId, the MSA view needs the msaview plugin and a
@@ -23,19 +23,36 @@ function isSessionWithWorkspaces(session) {
23
23
  // Missing BOTH is an embedded session: it has no workspaces, there is nothing
24
24
  // to ask for, and silence is the right answer.
25
25
  //
26
- // Missing ONE is a host that has workspaces but has changed how a view is
27
- // placed in them — and silence there is how this broke before. jbrowse-web
28
- // folded `setPendingMove` into its layout `init`, this guard went false, and
29
- // the plugin simply stopped asking for the split: no error, no missing
30
- // feature, just two views quietly stacking instead of sitting side by side.
31
- // Nobody noticed for weeks. Feature detection cannot ask the host to announce
32
- // a change, but it can tell "not supported here" from "supported, and gone".
26
+ // Missing ONE is a host that has workspaces but places views some other way —
27
+ // and silence there is how this broke before. jbrowse-web folded
28
+ // `setPendingMove` into its layout `init`, this guard went false, and the
29
+ // plugin simply stopped asking for the split: no error, no missing feature,
30
+ // just two views quietly stacking. Nobody noticed for weeks. Feature
31
+ // detection cannot ask the host to announce a change, but it can tell "not
32
+ // supported here" from "supported, and gone".
33
+ //
34
+ // Two very different hosts produce this one shape, and nothing on the session
35
+ // tells them apart, so the message carries both rather than the guess:
36
+ //
37
+ // - Releases through v4.3.0, where placement is `setPendingMoveToSplitRight`,
38
+ // a module function in @jbrowse/app-core's DockviewContext, not a session
39
+ // action. Nothing is wrong and nothing needs fixing; this plugin targets v5+
40
+ // and does not reach for v4's door. Measured 2026-08-17: v4.3.0 and latest
41
+ // stack, main splits.
42
+ // - A newer host that moved the action out from under us, which is the
43
+ // regression this warning exists to catch.
44
+ //
45
+ // Do not quiet the first case by sniffing the version. The alarm is only worth
46
+ // having if it fires on a shape it cannot explain, and these two shapes are
47
+ // identical.
33
48
  if (canEnable !== canPlace && !warnedPartial) {
34
49
  warnedPartial = true;
35
50
  console.warn(`jbrowse-plugin-protein3d: this session supports workspaces but not ` +
36
51
  `${canPlace ? 'setUseWorkspaces' : 'setPendingMove'}, so the ` +
37
- `side-by-side launch was skipped and the views will stack. The host's ` +
38
- `session API changed; the plugin needs updating to match.`);
52
+ `side-by-side launch was skipped and the views will stack. Expected on ` +
53
+ `releases through v4.3.0, which place views through @jbrowse/app-core ` +
54
+ `instead; on a newer host it means the session API moved and the plugin ` +
55
+ `needs updating to match.`);
39
56
  }
40
57
  return canEnable && canPlace;
41
58
  }
@@ -50,7 +50,7 @@ const AddStructureDialog = observer(function AddStructureDialog({ model, }) {
50
50
  (choice === 'pdb' && pdbId !== '') ||
51
51
  (choice === 'uniprot' && uniprotId !== '');
52
52
  return (React.createElement(Dialog, { open: true, onClose: handleClose, maxWidth: "sm", fullWidth: true },
53
- React.createElement(DialogTitle, null, "Add Structure"),
53
+ React.createElement(DialogTitle, null, "Add structure"),
54
54
  React.createElement(DialogContent, null,
55
55
  error ? React.createElement(ErrorMessage, { error: error }) : null,
56
56
  React.createElement(Typography, { variant: "body2", color: "text.secondary", sx: { mb: 2 } }, "Add another structure to superpose on the existing structure(s)."),
@@ -73,7 +73,7 @@ const AddStructureDialog = observer(function AddStructureDialog({ model, }) {
73
73
  }, placeholder: "https://files.rcsb.org/download/1CRN.cif", sx: { mb: 2 } })) : null,
74
74
  choice === 'file' ? (React.createElement("div", { style: { marginBottom: 16 } },
75
75
  React.createElement(Button, { variant: "outlined", component: "label" },
76
- file ? file.name : 'Choose File',
76
+ file ? file.name : 'Choose file',
77
77
  React.createElement("input", { type: "file", hidden: true, accept: ".pdb,.cif,.mmcif,.ent", onChange: ({ target }) => {
78
78
  const f = target.files?.[0];
79
79
  if (f) {
@@ -90,6 +90,6 @@ const AddStructureDialog = observer(function AddStructureDialog({ model, }) {
90
90
  } }, "Cancel"),
91
91
  React.createElement(Button, { onClick: () => {
92
92
  void handleAdd();
93
- }, variant: "contained", color: "primary", disabled: !canAdd }, "Add Structure"))));
93
+ }, variant: "contained", color: "primary", disabled: !canAdd }, "Add structure"))));
94
94
  });
95
95
  export default AddStructureDialog;
@@ -42,7 +42,7 @@ const ManualAlignmentDialog = observer(function ManualAlignmentDialog({ model, }
42
42
  return null;
43
43
  }
44
44
  return (React.createElement(Dialog, { open: true, onClose: handleClose, maxWidth: "md", fullWidth: true },
45
- React.createElement(DialogTitle, null, "Import Manual Alignment"),
45
+ React.createElement(DialogTitle, null, "Import manual alignment"),
46
46
  React.createElement(DialogContent, null,
47
47
  React.createElement(Typography, { variant: "body2", color: "text.secondary", sx: { mb: 2 } }, "Paste a pre-computed alignment in Clustal format. The first sequence should be the transcript and the second should be the structure."),
48
48
  React.createElement(TextField, { multiline: true, rows: 12, fullWidth: true, placeholder: `Example:
@@ -61,6 +61,6 @@ structure MKAAYLSMFGKEDHKPFGDDEVELFRAVPGLKLKIAG`, value: alignment, onChange:
61
61
  } }, "Cancel"),
62
62
  React.createElement(Button, { onClick: () => {
63
63
  handleApply();
64
- }, variant: "contained", color: "primary", disabled: !alignment.trim() }, "Apply Alignment"))));
64
+ }, variant: "contained", color: "primary", disabled: !alignment.trim() }, "Apply alignment"))));
65
65
  });
66
66
  export default ManualAlignmentDialog;