jbrowse-plugin-protein3d 0.4.4 → 0.4.5

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 (35) hide show
  1. package/dist/AddHighlightModel/ProteinToGenomeHighlightInner.js +1 -1
  2. package/dist/LaunchProteinView/components/AlphaFoldDBSearchStatus.js +3 -5
  3. package/dist/LaunchProteinView/components/FoldseekResultsTable.js +5 -21
  4. package/dist/LaunchProteinView/components/MSATable.js +3 -3
  5. package/dist/LaunchProteinView/components/UserProvidedStructure.js +6 -2
  6. package/dist/LaunchProteinView/components/launchProteinAnnotationView.js +3 -1
  7. package/dist/LaunchProteinView/hooks/useTranscriptSelection.js +3 -12
  8. package/dist/LaunchProteinView/services/foldseekApi.d.ts +1 -1
  9. package/dist/LaunchProteinView/utils/launchViewUtils.js +15 -4
  10. package/dist/LaunchProteinView/utils/util.js +3 -7
  11. package/dist/ProteinView/components/AddStructureDialog.js +8 -5
  12. package/dist/ProteinView/components/HeaderStructureInfo.js +1 -1
  13. package/dist/ProteinView/components/ProteinFeatureTrack.js +8 -12
  14. package/dist/ProteinView/components/ProteinViewHeader.js +1 -4
  15. package/dist/jbrowse-plugin-protein3d.umd.production.min.js +11 -11
  16. package/dist/jbrowse-plugin-protein3d.umd.production.min.js.map +3 -3
  17. package/dist/version.d.ts +1 -1
  18. package/dist/version.js +1 -1
  19. package/package.json +2 -2
  20. package/src/AddHighlightModel/ProteinToGenomeHighlightInner.tsx +1 -1
  21. package/src/LaunchProteinView/components/AlphaFoldDBSearchStatus.tsx +6 -4
  22. package/src/LaunchProteinView/components/FoldseekResultsTable.tsx +7 -24
  23. package/src/LaunchProteinView/components/MSATable.tsx +3 -3
  24. package/src/LaunchProteinView/components/UserProvidedStructure.tsx +6 -2
  25. package/src/LaunchProteinView/components/launchProteinAnnotationView.ts +3 -1
  26. package/src/LaunchProteinView/hooks/useTranscriptSelection.ts +7 -12
  27. package/src/LaunchProteinView/services/foldseekApi.ts +1 -1
  28. package/src/LaunchProteinView/services/lookupMethods.ts +9 -4
  29. package/src/LaunchProteinView/utils/launchViewUtils.ts +17 -4
  30. package/src/LaunchProteinView/utils/util.ts +5 -7
  31. package/src/ProteinView/components/AddStructureDialog.tsx +7 -7
  32. package/src/ProteinView/components/HeaderStructureInfo.tsx +1 -1
  33. package/src/ProteinView/components/ProteinFeatureTrack.tsx +12 -13
  34. package/src/ProteinView/components/ProteinViewHeader.tsx +9 -14
  35. package/src/version.ts +1 -1
@@ -12,6 +12,6 @@ const ProteinToGenomeHighlightInner = observer(function ProteinToGenomeHighlight
12
12
  const assembly = assemblyName
13
13
  ? assemblyManager.get(assemblyName)
14
14
  : undefined;
15
- return assembly && assemblyName ? (React.createElement(React.Fragment, null, proteinView?.structures.map((structure, idx) => structure[field].map((r, idx2) => (React.createElement(Highlight, { key: `${r.refName}-${r.start}-${r.end}-${idx}-${idx2}`, start: r.start, end: r.end, refName: r.refName, assemblyName: assemblyName, model: model })))))) : null;
15
+ return assembly && assemblyName ? (React.createElement(React.Fragment, null, proteinView?.structures.flatMap((structure, idx) => structure[field].map((r, idx2) => (React.createElement(Highlight, { key: `${r.refName}-${r.start}-${r.end}-${idx}-${idx2}`, start: r.start, end: r.end, refName: r.refName, assemblyName: assemblyName, model: model })))))) : null;
16
16
  });
17
17
  export default ProteinToGenomeHighlightInner;
@@ -10,15 +10,13 @@ function NotFound({ uniprotId }) {
10
10
  React.createElement(ExternalLink, { href: `https://alphafold.ebi.ac.uk/search/text/${uniprotId}` }, "(search for results)")));
11
11
  }
12
12
  export default function AlphaFoldDBSearchStatus({ uniprotId, selectedTranscript, structureSequence, isoformSequences, url, }) {
13
- const url2 = uniprotId
14
- ? `https://www.uniprot.org/uniprotkb/${uniprotId}/entry`
15
- : undefined;
16
13
  const [showAllProteinSequences, setShowAllProteinSequences] = useState(false);
17
14
  return uniprotId ? (React.createElement(React.Fragment, null,
18
15
  React.createElement("div", null,
19
16
  React.createElement(Typography, null,
20
- "UniProt link: ",
21
- React.createElement(ExternalLink, { href: url2 }, uniprotId)),
17
+ "UniProt link:",
18
+ ' ',
19
+ React.createElement(ExternalLink, { href: `https://www.uniprot.org/uniprotkb/${uniprotId}/entry` }, uniprotId)),
22
20
  React.createElement(Typography, null,
23
21
  "AlphaFoldDB link: ",
24
22
  React.createElement(ExternalLink, { href: url }, url))),
@@ -22,27 +22,11 @@ const useStyles = makeStyles()({
22
22
  },
23
23
  });
24
24
  function flattenResults(results) {
25
- const hits = [];
26
- for (const dbResult of results.results) {
27
- if (!dbResult.alignments) {
28
- continue;
29
- }
30
- for (const alignmentGroup of dbResult.alignments) {
31
- if (!alignmentGroup) {
32
- continue;
33
- }
34
- for (const alignment of alignmentGroup) {
35
- if (!alignment) {
36
- continue;
37
- }
38
- hits.push({
39
- ...alignment,
40
- db: dbResult.db,
41
- structureUrl: getStructureUrlFromTarget(alignment.target, dbResult.db),
42
- });
43
- }
44
- }
45
- }
25
+ const hits = results.results.flatMap(dbResult => (dbResult.alignments ?? []).flat().map(alignment => ({
26
+ ...alignment,
27
+ db: dbResult.db,
28
+ structureUrl: getStructureUrlFromTarget(alignment.target, dbResult.db),
29
+ })));
46
30
  hits.sort((a, b) => (a.eval ?? Infinity) - (b.eval ?? Infinity));
47
31
  return hits.slice(0, 100);
48
32
  }
@@ -18,7 +18,7 @@ export default function MSATable({ structureName, structureSequence, isoformSequ
18
18
  { ...val, seq: stripStopCodon(val.seq) },
19
19
  ]));
20
20
  const exactMatchIsoformAndStructureSeq = Object.entries(removedStars).find(([_, val]) => structureSequence === val.seq);
21
- const sname = `${structureName || ''} (structure residues)`;
21
+ const sname = `${structureName} (structure residues)`;
22
22
  const maxKeyLen = max([
23
23
  sname.length,
24
24
  ...Object.entries(removedStars).map(([_, val]) => getTranscriptDisplayName(val.feature).length),
@@ -38,8 +38,8 @@ export default function MSATable({ structureName, structureSequence, isoformSequ
38
38
  ? `${getTranscriptDisplayName(exactMatchIsoformAndStructureSeq[1].feature).padEnd(maxKeyLen)}* ${exactMatchIsoformAndStructureSeq[1].seq}`
39
39
  : undefined,
40
40
  ...Object.entries(removedStars)
41
- .map(([_, val]) => `${getTranscriptDisplayName(val.feature).padEnd(maxKeyLen)} ${val.seq}`)
42
- .filter(([k]) => k !== exactMatchIsoformAndStructureSeq?.[0]),
41
+ .filter(([k]) => k !== exactMatchIsoformAndStructureSeq?.[0])
42
+ .map(([_, val]) => `${getTranscriptDisplayName(val.feature).padEnd(maxKeyLen)} ${val.seq}`),
43
43
  ]
44
44
  .filter(f => !!f)
45
45
  .join('\n'), slotProps: {
@@ -64,17 +64,21 @@ const UserProvidedStructure = observer(function UserProvidedStructure({ feature,
64
64
  !!structureSequence &&
65
65
  stripStopCodon(protein.seq) !== structureSequence;
66
66
  const handleLaunch = async () => {
67
- if (!protein || !selectedTranscript || !(structureURL || file)) {
67
+ if (!protein || !selectedTranscript) {
68
68
  return;
69
69
  }
70
70
  try {
71
71
  const structureData = file ? await file.text() : undefined;
72
+ const url = structureURL ? structureURL : undefined;
73
+ if (!url && !structureData) {
74
+ return;
75
+ }
72
76
  launch3DProteinView({
73
77
  session,
74
78
  view,
75
79
  feature,
76
80
  selectedTranscript,
77
- url: structureURL || undefined,
81
+ url,
78
82
  data: structureData,
79
83
  userProvidedTranscriptSequence: protein.seq,
80
84
  alignmentAlgorithm,
@@ -23,7 +23,9 @@ export async function launchProteinAnnotationView({ session, feature, selectedTr
23
23
  uniprotId,
24
24
  getGeneDisplayName(feature),
25
25
  getTranscriptDisplayName(selectedTranscript),
26
- ].join(' - '),
26
+ ]
27
+ .filter(s => !!s)
28
+ .join(' - '),
27
29
  });
28
30
  // Register the 1D view for linked highlighting
29
31
  if (connectedViewId && selectedTranscript) {
@@ -2,18 +2,9 @@ import { useMemo, useState } from 'react';
2
2
  import { selectBestTranscript } from '../utils/util';
3
3
  export default function useTranscriptSelection({ options, isoformSequences, structureSequence, }) {
4
4
  const [userSelection, setUserSelection] = useState();
5
- // SYNC: src/LaunchProteinView/hooks/useAlphaFoldDBSearch.ts (same pattern)
6
- // Auto-select synchronously to avoid render gap
7
- const autoSelection = useMemo(() => {
8
- if (isoformSequences !== undefined && userSelection === undefined) {
9
- return selectBestTranscript({
10
- options,
11
- isoformSequences,
12
- structureSequence,
13
- })?.id();
14
- }
15
- return undefined;
16
- }, [options, structureSequence, isoformSequences, userSelection]);
5
+ const autoSelection = useMemo(() => isoformSequences !== undefined
6
+ ? selectBestTranscript({ options, isoformSequences, structureSequence })?.id()
7
+ : undefined, [options, structureSequence, isoformSequences]);
17
8
  const effectiveSelection = userSelection ?? autoSelection;
18
9
  return { userSelection: effectiveSelection, setUserSelection };
19
10
  }
@@ -55,7 +55,7 @@ export interface FoldseekAlignment {
55
55
  }
56
56
  export interface FoldseekDatabaseResult {
57
57
  db: string;
58
- alignments?: ((FoldseekAlignment | undefined)[] | undefined)[];
58
+ alignments?: FoldseekAlignment[][];
59
59
  }
60
60
  export interface FoldseekResult {
61
61
  query: {
@@ -54,12 +54,13 @@ function formatViewName(prefix, feature, selectedTranscript, uniprotId) {
54
54
  getGeneDisplayName(feature),
55
55
  getTranscriptDisplayName(selectedTranscript),
56
56
  ]),
57
- ].join(' - ');
57
+ ]
58
+ .filter(s => !!s)
59
+ .join(' - ');
58
60
  }
59
61
  export function launch3DProteinView({ session, view, feature, selectedTranscript, uniprotId, url, data, userProvidedTranscriptSequence, alignmentAlgorithm, displayName, connectedMsaViewId, }) {
60
- return session.addView('ProteinView', {
62
+ const snap = {
61
63
  type: 'ProteinView',
62
- isFloating: true,
63
64
  alignmentAlgorithm,
64
65
  connectedMsaViewId,
65
66
  structures: [
@@ -73,7 +74,17 @@ export function launch3DProteinView({ session, view, feature, selectedTranscript
73
74
  ],
74
75
  displayName: displayName ??
75
76
  formatViewName('Protein view', feature, selectedTranscript, uniprotId),
76
- });
77
+ };
78
+ // eslint-disable-next-line no-console
79
+ console.log('[protein3d debug] addView ProteinView snapshot:', JSON.stringify(snap, (_k, v) => (typeof v === 'function' ? '<fn>' : v), 2));
80
+ try {
81
+ return session.addView('ProteinView', snap);
82
+ }
83
+ catch (e) {
84
+ // eslint-disable-next-line no-console
85
+ console.log('[protein3d debug] addView threw:', e.message);
86
+ throw e;
87
+ }
77
88
  }
78
89
  export async function launch1DProteinView({ session, view, feature, selectedTranscript, uniprotId, confidenceUrl, }) {
79
90
  if (!uniprotId || !isSessionWithAddTracks(session)) {
@@ -64,13 +64,9 @@ export function isRecognizedDatabaseId(id) {
64
64
  * Get the database type for a recognized ID (used for UniProt xref queries)
65
65
  */
66
66
  export function getDatabaseTypeForId(id) {
67
- if (ensemblGenePattern.test(id)) {
68
- return 'ensembl';
69
- }
70
- if (ensemblTranscriptPattern.test(id)) {
71
- return 'ensembl';
72
- }
73
- if (ensemblProteinPattern.test(id)) {
67
+ if (ensemblGenePattern.test(id) ||
68
+ ensemblTranscriptPattern.test(id) ||
69
+ ensemblProteinPattern.test(id)) {
74
70
  return 'ensembl';
75
71
  }
76
72
  if (refSeqTranscriptPattern.test(id) || refSeqProteinPattern.test(id)) {
@@ -21,19 +21,22 @@ const AddStructureDialog = observer(function AddStructureDialog({ model, }) {
21
21
  };
22
22
  const handleAdd = async () => {
23
23
  try {
24
- let url = structureURL;
24
+ let url;
25
25
  let data;
26
26
  if (choice === 'pdb' && pdbId) {
27
27
  url = getPdbStructureUrl(pdbId);
28
28
  }
29
- if (choice === 'uniprot' && uniprotId) {
29
+ else if (choice === 'uniprot' && uniprotId) {
30
30
  url = getAlphaFoldStructureUrl(uniprotId.toUpperCase());
31
31
  }
32
- if (choice === 'file' && file) {
32
+ else if (choice === 'url' && structureURL) {
33
+ url = structureURL;
34
+ }
35
+ else if (choice === 'file' && file) {
33
36
  data = await file.text();
34
37
  }
35
- if (url || data) {
36
- await model.addStructureAndSuperpose({ url: url || undefined, data });
38
+ if (url !== undefined || data !== undefined) {
39
+ await model.addStructureAndSuperpose({ url, data });
37
40
  handleClose();
38
41
  }
39
42
  }
@@ -4,7 +4,7 @@ const HeaderStructureInfo = observer(function HeaderStructureInfo({ model, }) {
4
4
  const { structures } = model;
5
5
  return structures.map((structure, idx) => {
6
6
  const { hoverString } = structure;
7
- return (React.createElement("span", { key: `${hoverString}-${idx}` },
7
+ return (React.createElement("span", { key: idx },
8
8
  hoverString ? `Hover: ${hoverString}` : '',
9
9
  "\u00A0"));
10
10
  });
@@ -5,6 +5,13 @@ import { CHAR_WIDTH, HIDE_BUTTON_COLOR, HOVERED_BORDER, HOVER_MARKER_COLOR, SELE
5
5
  import { selectResidueRange } from '../highlightResidueRange';
6
6
  import { getFeatureColor } from '../hooks/useUniProtFeatures';
7
7
  import { clickProteinToGenome } from '../proteinToGenomeMapping';
8
+ function getFeatureAlignmentRange(feature, structurePositionToAlignmentMap) {
9
+ const startAlignmentPos = structurePositionToAlignmentMap?.[feature.start - 1];
10
+ const endAlignmentPos = structurePositionToAlignmentMap?.[feature.end - 1];
11
+ return startAlignmentPos !== undefined && endAlignmentPos !== undefined
12
+ ? { start: startAlignmentPos, end: endAlignmentPos }
13
+ : undefined;
14
+ }
8
15
  function getVisibleTypes(featureTypes, hiddenFeatureTypes) {
9
16
  return featureTypes.filter(type => !hiddenFeatureTypes.has(type));
10
17
  }
@@ -31,20 +38,9 @@ const FeatureBar = observer(function FeatureBar({ feature, model, }) {
31
38
  const [isHovered, setIsHovered] = useState(false);
32
39
  const { molstarPluginContext, selectedFeatureId, structurePositionToAlignmentMap, } = model;
33
40
  const isSelected = selectedFeatureId === feature.uniqueId;
34
- const getAlignmentRange = () => {
35
- if (!structurePositionToAlignmentMap) {
36
- return undefined;
37
- }
38
- const startAlignmentPos = structurePositionToAlignmentMap[feature.start - 1];
39
- const endAlignmentPos = structurePositionToAlignmentMap[feature.end - 1];
40
- if (startAlignmentPos !== undefined && endAlignmentPos !== undefined) {
41
- return { start: startAlignmentPos, end: endAlignmentPos };
42
- }
43
- return undefined;
44
- };
45
41
  const handleMouseEnter = () => {
46
42
  setIsHovered(true);
47
- const range = getAlignmentRange();
43
+ const range = getFeatureAlignmentRange(feature, structurePositionToAlignmentMap);
48
44
  if (range) {
49
45
  model.setAlignmentHoverRange(range);
50
46
  }
@@ -31,10 +31,7 @@ const ProteinViewHeader = observer(function ProteinViewHeader({ model, }) {
31
31
  model.setAutoScrollAlignment(!autoScrollAlignment);
32
32
  } }))),
33
33
  showAlignment
34
- ? structures.map((structure, idx) => {
35
- const { pairwiseAlignment } = structure;
36
- return (React.createElement("div", { key: idx }, pairwiseAlignment ? (React.createElement(ProteinAlignment, { key: idx, model: structure })) : (React.createElement(LoadingEllipses, { message: "Loading pairwise alignment" }))));
37
- })
34
+ ? structures.map((structure, idx) => (React.createElement("div", { key: idx }, structure.pairwiseAlignment ? (React.createElement(ProteinAlignment, { model: structure })) : (React.createElement(LoadingEllipses, { message: "Loading pairwise alignment" })))))
38
35
  : null,
39
36
  React.createElement(AddStructureDialog, { model: model })));
40
37
  });