jbrowse-plugin-protein3d 1.2.0 → 1.3.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 (40) hide show
  1. package/README.md +5 -3
  2. package/dist/LaunchProteinView/utils/util.js +23 -3
  3. package/dist/ProteinView/components/FeatureBar.js +5 -4
  4. package/dist/ProteinView/components/FeatureTypeLabel.js +2 -2
  5. package/dist/ProteinView/components/ProteinAlignment.js +15 -22
  6. package/dist/ProteinView/components/ProteinFeatureTrack.js +7 -7
  7. package/dist/ProteinView/components/ResidueValueTrack.js +4 -4
  8. package/dist/ProteinView/constants.js +10 -0
  9. package/dist/ProteinView/hooks/useProteinFeatureTrackData.js +41 -15
  10. package/dist/ProteinView/hooks/useUniProtFeatures.js +6 -15
  11. package/dist/ProteinView/model.js +18 -0
  12. package/dist/ProteinView/structureModel.js +16 -7
  13. package/dist/chunks/ProteinView-JMWV7XDK.js +9 -0
  14. package/dist/chunks/ProteinView-JMWV7XDK.js.map +7 -0
  15. package/dist/chunks/chunk-UAUJEOAS.js +16 -0
  16. package/dist/chunks/chunk-UAUJEOAS.js.map +7 -0
  17. package/dist/jbrowse-plugin-protein3d.esm.js +7 -7
  18. package/dist/jbrowse-plugin-protein3d.esm.js.map +4 -4
  19. package/dist/version.js +1 -1
  20. package/package.json +2 -2
  21. package/src/LaunchProteinView/utils/util.ts +28 -3
  22. package/src/ProteinView/components/FeatureBar.tsx +6 -3
  23. package/src/ProteinView/components/FeatureTypeLabel.tsx +2 -2
  24. package/src/ProteinView/components/ProteinAlignment.tsx +79 -69
  25. package/src/ProteinView/components/ProteinFeatureTrack.tsx +8 -11
  26. package/src/ProteinView/components/ResidueValueTrack.tsx +4 -4
  27. package/src/ProteinView/constants.ts +20 -0
  28. package/src/ProteinView/hooks/layoutFeature.test.ts +35 -37
  29. package/src/ProteinView/hooks/packLanes.test.ts +7 -1
  30. package/src/ProteinView/hooks/useProteinFeatureTrackData.ts +49 -15
  31. package/src/ProteinView/hooks/useUniProtFeatures.ts +6 -15
  32. package/src/ProteinView/model.test.ts +21 -0
  33. package/src/ProteinView/model.ts +21 -0
  34. package/src/ProteinView/proteinViewSpec.ts +1 -0
  35. package/src/ProteinView/structureModel.ts +19 -10
  36. package/src/version.ts +1 -1
  37. package/dist/chunks/ProteinView-B57QOH3K.js +0 -9
  38. package/dist/chunks/ProteinView-B57QOH3K.js.map +0 -7
  39. package/dist/chunks/chunk-UTUOAPWU.js +0 -16
  40. package/dist/chunks/chunk-UTUOAPWU.js.map +0 -7
package/README.md CHANGED
@@ -37,9 +37,11 @@ Using the plugin:
37
37
  gene, and reads the R248 hotspot back to its codon;
38
38
  [Proteins on genomes.jbrowse.org](https://jbrowse.org/jb2/docs/tutorials/genomes_proteins/)
39
39
  launches a structure and an MSA from any gene's right-click menu.
40
- - [Demos](docs/demos.md): structures that are easy to map wrong, each one link
41
- away — a peptide bound to a larger partner, a protein bound to DNA, a receptor
42
- with another protein fused into it, a phosphorylated residue, and a
40
+ - [Demos](docs/demos.md): one link each. Genes opened by name alone in human,
41
+ mouse, yeast, bacteria, fungi and viruses (E. coli recA, M. tuberculosis katG,
42
+ the SARS-CoV-2 spike, the HIV-1 capsid), then the structures that are easy to
43
+ map wrong — a peptide bound to a larger partner, a protein bound to DNA, a
44
+ receptor with another protein fused into it, a phosphorylated residue, and a
43
45
  mitochondrial protein.
44
46
  - [Your own structures](docs/your-own-structures.md): opening a model you folded
45
47
  yourself (ColabFold, AlphaFold 3, Boltz…) instead of the AlphaFold DB one,
@@ -122,6 +122,15 @@ export function findRecognizedDbIds(f) {
122
122
  }
123
123
  return [...new Set(recognizedIds)];
124
124
  }
125
+ // `UniProtKB/Swiss-Prot:P0A7G6`, the reviewed entry before a TrEMBL one
126
+ function dbxrefUniProtId(f) {
127
+ const entries = parseDbxref(f?.get('Dbxref') ?? f?.get('dbxref') ?? f?.get('db_xref'));
128
+ return ['UniProtKB/Swiss-Prot:', 'UniProtKB/TrEMBL:']
129
+ .flatMap(prefix => entries
130
+ .filter(e => e.startsWith(prefix))
131
+ .map(e => e.slice(prefix.length)))
132
+ .find(id => id.length > 0);
133
+ }
125
134
  /**
126
135
  * Extract all useful identifiers from a feature for UniProt lookup.
127
136
  * If the feature is a gene, prioritizes identifiers from the preferred
@@ -140,13 +149,24 @@ export function extractFeatureIdentifiers(f, preferredTranscriptId) {
140
149
  transcripts[0] ??
141
150
  f;
142
151
  }
143
- // --- Extracting Recognized IDs and UniProt ID from featureToProcess ---
144
- const recognizedIds = findRecognizedDbIds(featureToProcess);
152
+ // NCBI's GFF3 puts the protein on the CDS record, not the transcript: its
153
+ // RefSeq accession always, and for a curated genome (E. coli K-12, yeast)
154
+ // the UniProt accession itself. A prokaryotic gene has no transcript record
155
+ // between, so the CDS is the only place either appears.
156
+ const cds = featureToProcess
157
+ .get('subfeatures')
158
+ ?.find(sub => sub.get('type')?.toLowerCase() === 'cds');
159
+ const recognizedIds = [
160
+ ...findRecognizedDbIds(featureToProcess),
161
+ ...findRecognizedDbIds(cds),
162
+ ];
145
163
  // Handle UniProt ID from feature attributes (trust that it's valid if present)
146
164
  const uniprotIdAttr = featureToProcess.get('uniprot') ??
147
165
  featureToProcess.get('uniprotId') ??
148
166
  featureToProcess.get('uniprotid') ??
149
- featureToProcess.get('UniProt');
167
+ featureToProcess.get('UniProt') ??
168
+ dbxrefUniProtId(featureToProcess) ??
169
+ dbxrefUniProtId(cds);
150
170
  const uniprotId = typeof uniprotIdAttr === 'string' && uniprotIdAttr.length > 0
151
171
  ? uniprotIdAttr
152
172
  : undefined;
@@ -23,16 +23,17 @@ function FeatureTooltipContent({ feature, layout, model, }) {
23
23
  "Structure residue: ",
24
24
  first,
25
25
  "-",
26
- last)) : null,
26
+ last,
27
+ layout.clipped ? ' (the structure lacks the rest)' : '')) : null,
27
28
  feature.description ? React.createElement("div", null, feature.description) : null));
28
29
  }
29
30
  // `selected` comes from the track rather than each bar reading the model's
30
31
  // selection itself, so a click re-renders the two bars it changes instead of
31
32
  // every bar of every track (p53 has 1,363 natural variants).
32
- const FeatureBar = observer(function FeatureBar({ layout, top, selected, model, }) {
33
+ const FeatureBar = observer(function FeatureBar({ layout, top, height, selected, model, }) {
33
34
  const [isHovered, setIsHovered] = useState(false);
34
35
  const { feature, alignmentStart, alignmentEnd } = layout;
35
- const { columnWidth, trackHeight } = model;
36
+ const { columnWidth } = model;
36
37
  return (React.createElement(Tooltip, { title: React.createElement(FeatureTooltipContent, { feature: feature, layout: layout, model: model }), followCursor: true },
37
38
  React.createElement("div", { "data-testid": `protein-feature-${feature.type}`, "data-feature-id": feature.uniqueId, "data-feature-start": feature.start, "data-feature-end": feature.end, onClick: () => {
38
39
  if (selected) {
@@ -64,7 +65,7 @@ const FeatureBar = observer(function FeatureBar({ layout, top, selected, model,
64
65
  left: alignmentStart * columnWidth,
65
66
  top,
66
67
  width: (alignmentEnd - alignmentStart + 1) * columnWidth,
67
- height: trackHeight,
68
+ height,
68
69
  backgroundColor: getFeatureColor(feature.type),
69
70
  opacity: isHovered || selected ? 0.9 : 0.6,
70
71
  cursor: 'pointer',
@@ -4,10 +4,10 @@ import UnfoldLessIcon from '@mui/icons-material/UnfoldLess';
4
4
  import UnfoldMoreIcon from '@mui/icons-material/UnfoldMore';
5
5
  import { IconButton, Tooltip } from '@mui/material';
6
6
  import { observer } from 'mobx-react';
7
- import { HIDE_BUTTON_COLOR } from '../constants';
7
+ import { HIDE_BUTTON_COLOR, NORMAL_TRACK_HEIGHT } from '../constants';
8
8
  const FeatureTypeLabel = observer(function FeatureTypeLabel({ type, laneCount, model, }) {
9
9
  const expanded = model.expandedFeatureTypes.has(type);
10
- const iconSize = model.trackHeight;
10
+ const iconSize = Math.min(model.trackHeight, NORMAL_TRACK_HEIGHT);
11
11
  return (React.createElement(Tooltip, { title: type, placement: "left" },
12
12
  React.createElement("div", { style: {
13
13
  height: '100%',
@@ -1,4 +1,5 @@
1
1
  import React, { useEffect, useRef } from 'react';
2
+ import { ResizeHandle } from '@jbrowse/core/ui';
2
3
  import { Tooltip } from '@mui/material';
3
4
  import { autorun } from 'mobx';
4
5
  import { observer } from 'mobx-react';
@@ -9,7 +10,7 @@ import { ColorKey, GradientKey } from './ColorKey';
9
10
  import ColumnOverlays, { SelectionBackdrop } from './ColumnOverlays';
10
11
  import FeatureTypeLabel from './FeatureTypeLabel';
11
12
  import MismatchShading from './MismatchShading';
12
- import ProteinFeatureTrack, { featureTrackHeight } from './ProteinFeatureTrack';
13
+ import ProteinFeatureTrack from './ProteinFeatureTrack';
13
14
  import ResidueValueTrack from './ResidueValueTrack';
14
15
  import SplitString from './SplitString';
15
16
  import { followHover, offScreenCenterTarget } from '../autoScroll';
@@ -48,7 +49,7 @@ function GutterLabel({ label, title, }) {
48
49
  } }, label)));
49
50
  }
50
51
  const ProteinAlignment = observer(function ProteinAlignment({ model, }) {
51
- const { alignment, showProteinTracks, showAllFeatureTracks, label, confidenceCells, columnWidth, trackHeight, trackGap, } = model;
52
+ const { alignment, showProteinTracks, showAllFeatureTracks, label, confidenceCells, columnWidth, laneHeight, } = model;
52
53
  const hydrophobicityCells = showAllFeatureTracks
53
54
  ? model.hydrophobicityCells
54
55
  : [];
@@ -102,7 +103,6 @@ const ProteinAlignment = observer(function ProteinAlignment({ model, }) {
102
103
  return null;
103
104
  }
104
105
  const columns = alignmentLength(alignment);
105
- const valueRowHeight = trackHeight + trackGap;
106
106
  const sequenceRow = (key, rowLabel, title, str, shading) => ({
107
107
  key,
108
108
  height: ROW_HEIGHT,
@@ -131,6 +131,11 @@ const ProteinAlignment = observer(function ProteinAlignment({ model, }) {
131
131
  },
132
132
  ];
133
133
  const sequenceHeight = sequenceRows.length * ROW_HEIGHT;
134
+ let trackLanes = 0;
135
+ const trackRow = (key, lanes, rowLabel, content) => {
136
+ trackLanes += lanes;
137
+ rows.push({ key, height: lanes * laneHeight, label: rowLabel, content });
138
+ };
134
139
  if (showProteinTracks) {
135
140
  if (featureStatus) {
136
141
  rows.push({
@@ -141,28 +146,13 @@ const ProteinAlignment = observer(function ProteinAlignment({ model, }) {
141
146
  });
142
147
  }
143
148
  for (const group of groups ?? []) {
144
- rows.push({
145
- key: `feature-${group.type}`,
146
- height: featureTrackHeight(model, group),
147
- label: (React.createElement(FeatureTypeLabel, { type: group.type, laneCount: group.laneCount, model: model })),
148
- content: React.createElement(ProteinFeatureTrack, { group: group, model: model }),
149
- });
149
+ trackRow(`feature-${group.type}`, model.expandedFeatureTypes.has(group.type) ? group.laneCount : 1, React.createElement(FeatureTypeLabel, { type: group.type, laneCount: group.laneCount, model: model }), React.createElement(ProteinFeatureTrack, { group: group, model: model }));
150
150
  }
151
151
  if (confidenceCells.length > 0) {
152
- rows.push({
153
- key: 'plddt',
154
- height: valueRowHeight,
155
- label: (React.createElement(GutterLabel, { label: "pLDDT", title: React.createElement(ColorKey, { title: "AlphaFold per-residue confidence (pLDDT)", entries: PLDDT_BANDS, color: "inherit" }) })),
156
- content: (React.createElement(ResidueValueTrack, { cells: confidenceCells, colorFor: plddtColor, formatValue: v => `pLDDT ${v.toFixed(0)}`, model: model })),
157
- });
152
+ trackRow('plddt', 1, React.createElement(GutterLabel, { label: "pLDDT", title: React.createElement(ColorKey, { title: "AlphaFold per-residue confidence (pLDDT)", entries: PLDDT_BANDS, color: "inherit" }) }), React.createElement(ResidueValueTrack, { cells: confidenceCells, colorFor: plddtColor, formatValue: v => `pLDDT ${v.toFixed(0)}`, model: model }));
158
153
  }
159
154
  if (hydrophobicityCells.length > 0) {
160
- rows.push({
161
- key: 'hydrophobicity',
162
- height: valueRowHeight,
163
- label: (React.createElement(GutterLabel, { label: "hydro", title: React.createElement(GradientKey, { title: "Kyte-Doolittle hydrophobicity", testId: "hydrophobicity-legend", minLabel: "hydrophilic", maxLabel: "hydrophobic", colors: HYDROPHOBICITY_KEY_SCORES.map(score => hydrophobicityColor(score)), color: "inherit" }) })),
164
- content: (React.createElement(ResidueValueTrack, { cells: hydrophobicityCells, colorFor: hydrophobicityColor, formatValue: v => `Kyte-Doolittle ${v.toFixed(1)}`, model: model })),
165
- });
155
+ trackRow('hydrophobicity', 1, React.createElement(GutterLabel, { label: "hydro", title: React.createElement(GradientKey, { title: "Kyte-Doolittle hydrophobicity", testId: "hydrophobicity-legend", minLabel: "hydrophilic", maxLabel: "hydrophobic", colors: HYDROPHOBICITY_KEY_SCORES.map(score => hydrophobicityColor(score)), color: "inherit" }) }), React.createElement(ResidueValueTrack, { cells: hydrophobicityCells, colorFor: hydrophobicityColor, formatValue: v => `Kyte-Doolittle ${v.toFixed(1)}`, model: model }));
166
156
  }
167
157
  }
168
158
  const columnAt = (event) => {
@@ -213,6 +203,9 @@ const ProteinAlignment = observer(function ProteinAlignment({ model, }) {
213
203
  }
214
204
  }
215
205
  : undefined }, row.content))),
216
- React.createElement(ColumnOverlays, { model: model }))))));
206
+ React.createElement(ColumnOverlays, { model: model })))),
207
+ trackLanes > 0 ? (React.createElement(ResizeHandle, { bar: true, title: "Drag to resize the feature tracks", "data-testid": "feature-tracks-resize-handle", gain: trackLanes, onDrag: distance => {
208
+ model.resizeTracks(distance);
209
+ } })) : null));
217
210
  });
218
211
  export default ProteinAlignment;
@@ -1,13 +1,13 @@
1
1
  import React from 'react';
2
2
  import { observer } from 'mobx-react';
3
3
  import FeatureBar from './FeatureBar';
4
- export function featureTrackHeight(model, group) {
5
- const lanes = model.expandedFeatureTypes.has(group.type) ? group.laneCount : 1;
6
- return lanes * (model.trackHeight + model.trackGap);
7
- }
4
+ // Bars are placed as fractions of the row, which alone carries the px height:
5
+ // dragging the resize handle then restyles one row instead of re-rendering
6
+ // every bar (p53 has 1,432, and a drag cost 180ms a frame).
8
7
  const ProteinFeatureTrack = observer(function ProteinFeatureTrack({ group, model, }) {
9
- const { selectedFeatureId, trackHeight, trackGap } = model;
10
- const expanded = model.expandedFeatureTypes.has(group.type);
11
- return group.layouts.map(layout => (React.createElement(FeatureBar, { key: layout.feature.uniqueId, layout: layout, top: (expanded ? layout.lane : 0) * (trackHeight + trackGap), selected: selectedFeatureId === layout.feature.uniqueId, model: model })));
8
+ const { selectedFeatureId, trackGap } = model;
9
+ const lanes = model.expandedFeatureTypes.has(group.type) ? group.laneCount : 1;
10
+ const height = `calc(${100 / lanes}% - ${trackGap}px)`;
11
+ return group.layouts.map(layout => (React.createElement(FeatureBar, { key: layout.feature.uniqueId, layout: layout, top: `${(Math.min(layout.lane, lanes - 1) / lanes) * 100}%`, height: height, selected: selectedFeatureId === layout.feature.uniqueId, model: model })));
12
12
  });
13
13
  export default ProteinFeatureTrack;
@@ -2,12 +2,12 @@ import React, { useMemo } from 'react';
2
2
  import { Tooltip } from '@mui/material';
3
3
  import { observer } from 'mobx-react';
4
4
  const Cells = observer(function Cells({ cells, colorFor, model, }) {
5
- const { columnWidth, trackHeight } = model;
6
- return (React.createElement("div", { style: { position: 'relative', height: trackHeight } }, cells.map(cell => (React.createElement("div", { key: cell.col, style: {
5
+ const { columnWidth, trackGap } = model;
6
+ return (React.createElement("div", { style: { position: 'relative', height: `calc(100% - ${trackGap}px)` } }, cells.map(cell => (React.createElement("div", { key: cell.col, style: {
7
7
  position: 'absolute',
8
8
  left: cell.col * columnWidth,
9
9
  width: columnWidth,
10
- height: trackHeight,
10
+ height: '100%',
11
11
  backgroundColor: colorFor(cell.value),
12
12
  } })))));
13
13
  });
@@ -24,7 +24,7 @@ const ResidueValueTrack = observer(function ResidueValueTrack({ cells, colorFor,
24
24
  ? undefined
25
25
  : valueByCol.get(alignmentHoverPos);
26
26
  return (React.createElement(Tooltip, { title: hoveredValue === undefined ? '' : formatValue(hoveredValue), followCursor: true },
27
- React.createElement("div", null,
27
+ React.createElement("div", { style: { height: '100%' } },
28
28
  React.createElement(Cells, { cells: cells, colorFor: colorFor, model: model }))));
29
29
  });
30
30
  export default ResidueValueTrack;
@@ -4,7 +4,17 @@ export const COMPACT_TRACK_HEIGHT = 8;
4
4
  export const COMPACT_TRACK_GAP = 1;
5
5
  export const NORMAL_TRACK_HEIGHT = 12;
6
6
  export const NORMAL_TRACK_GAP = 2;
7
+ export const MIN_TRACK_HEIGHT = 2;
8
+ export const MAX_TRACK_HEIGHT = 40;
7
9
  export const LABEL_WIDTH = 124;
10
+ export function trackHeightOf({ trackHeight, compactTracks, }) {
11
+ return (trackHeight ?? (compactTracks ? COMPACT_TRACK_HEIGHT : NORMAL_TRACK_HEIGHT));
12
+ }
13
+ export function trackGapOf(trackHeight) {
14
+ return trackHeight < NORMAL_TRACK_HEIGHT
15
+ ? COMPACT_TRACK_GAP
16
+ : NORMAL_TRACK_GAP;
17
+ }
8
18
  export const MINOR_FEATURE_TYPES = new Set([
9
19
  'Chain',
10
20
  'Helix',
@@ -1,5 +1,8 @@
1
1
  import { useMemo } from 'react';
2
2
  import useUniProtFeatures from './useUniProtFeatures';
3
+ // A bond's two positions are its whole meaning, so one residue of a pair says
4
+ // nothing true about the structure.
5
+ const ENDPOINT_PAIR_TYPES = new Set(['Disulfide bond', 'Cross-link']);
3
6
  /**
4
7
  * Places a UniProt feature: its 1-based inclusive UniProt range becomes a
5
8
  * 0-based half-open structure range (identity for AlphaFold, SIFTS-offset for
@@ -7,26 +10,41 @@ import useUniProtFeatures from './useUniProtFeatures';
7
10
  * UniProt->structure coordinate conversion in the tracks; every consumer reads
8
11
  * `structureStart`/`structureEnd` off the layout.
9
12
  *
10
- * Returns undefined when either endpoint falls outside the structure or has no
11
- * alignment column, so an unmappable feature is dropped rather than drawn at a
12
- * misleading position.
13
+ * A feature reaching past the residues the structure has is clipped to the
14
+ * ones it does have, and says so (`clipped`): a crystallized fragment used to
15
+ * lose every region straddling the construct's ends. Returns undefined when no
16
+ * residue of the feature has an alignment column.
13
17
  */
14
18
  export function layoutFeature(feature, structurePositionToAlignmentMap, mapUniProtPosition) {
15
- const structureStart = mapUniProtPosition(feature.start);
16
- const structureLast = mapUniProtPosition(feature.end);
17
- if (structureStart === undefined || structureLast === undefined) {
18
- return undefined;
19
+ const placed = (uniprotPos) => {
20
+ const structurePos = mapUniProtPosition(uniprotPos);
21
+ const column = structurePos === undefined
22
+ ? undefined
23
+ : structurePositionToAlignmentMap[structurePos];
24
+ return structurePos === undefined || column === undefined
25
+ ? undefined
26
+ : { structurePos, column };
27
+ };
28
+ let first = feature.start;
29
+ let start = placed(first);
30
+ while (!start && first < feature.end) {
31
+ start = placed(++first);
19
32
  }
20
- const alignmentStart = structurePositionToAlignmentMap[structureStart];
21
- const alignmentEnd = structurePositionToAlignmentMap[structureLast];
22
- return alignmentStart === undefined || alignmentEnd === undefined
33
+ let last = feature.end;
34
+ let end = placed(last);
35
+ while (!end && last > first) {
36
+ end = placed(--last);
37
+ }
38
+ const clipped = first !== feature.start || last !== feature.end;
39
+ return !start || !end || (clipped && ENDPOINT_PAIR_TYPES.has(feature.type))
23
40
  ? undefined
24
41
  : {
25
42
  feature,
26
- structureStart,
27
- structureEnd: structureLast + 1,
28
- alignmentStart,
29
- alignmentEnd,
43
+ structureStart: start.structurePos,
44
+ structureEnd: end.structurePos + 1,
45
+ alignmentStart: start.column,
46
+ alignmentEnd: end.column,
47
+ clipped,
30
48
  lane: 0,
31
49
  };
32
50
  }
@@ -52,6 +70,14 @@ export function packLanes(layouts) {
52
70
  }
53
71
  return Math.max(laneEnds.length, 1);
54
72
  }
73
+ /**
74
+ * Paint order for a collapsed track, where every bar shares one row: a wide
75
+ * bar painted last covers the short ones inside it, which can then be neither
76
+ * hovered nor clicked.
77
+ */
78
+ export function widestFirst(layouts) {
79
+ return [...layouts].sort((a, b) => b.alignmentEnd - b.alignmentStart - (a.alignmentEnd - a.alignmentStart));
80
+ }
55
81
  export default function useProteinFeatureTrackData(model, uniprotId, mapUniProtPosition) {
56
82
  const { features, isLoading, error } = useUniProtFeatures(uniprotId);
57
83
  const { omittedFeatureTypes, structurePositionToAlignmentMap } = model;
@@ -76,8 +102,8 @@ export default function useProteinFeatureTrackData(model, uniprotId, mapUniProtP
76
102
  }
77
103
  return [...byType].map(([type, layouts]) => ({
78
104
  type,
79
- layouts,
80
105
  laneCount: packLanes(layouts),
106
+ layouts: widestFirst(layouts),
81
107
  }));
82
108
  }, [
83
109
  features,
@@ -56,26 +56,17 @@ async function fetchUniProtFeatures(url) {
56
56
  const start = Number.parseInt(parts[3] ?? '', 10);
57
57
  const end = Number.parseInt(parts[4] ?? '', 10);
58
58
  const attributes = parts[8] ?? '';
59
- let description = '';
60
- let id;
61
- for (const attr of attributes.split(';')) {
62
- const [key, value] = attr.split('=');
63
- if (key === 'Note') {
64
- description = decodeURIComponent(value ?? '');
65
- }
66
- else if (key === 'ID') {
67
- id = value;
68
- }
69
- }
59
+ const note = attributes
60
+ .split(';')
61
+ .find(attr => attr.startsWith('Note='))
62
+ ?.slice('Note='.length);
70
63
  if (type && start >= 1 && end >= start) {
71
- const uniqueId = `${type}-${start}-${end}-${features.length}`;
72
64
  features.push({
73
65
  type,
74
66
  start,
75
67
  end,
76
- description,
77
- id,
78
- uniqueId,
68
+ description: decodeURIComponent(note ?? ''),
69
+ uniqueId: `${type}-${start}-${end}-${features.length}`,
79
70
  });
80
71
  }
81
72
  }
@@ -4,6 +4,7 @@ import { addDisposer, getPath, types } from '@jbrowse/mobx-state-tree';
4
4
  import { autorun } from 'mobx';
5
5
  import { ALIGNMENT_ALGORITHM_VALUES, DEFAULT_ALIGNMENT_ALGORITHM, } from 'p2s_mapper';
6
6
  import { COLOR_SCHEME_VALUES, applyColorTheme, colorSchemeLegend, } from './applyColorTheme';
7
+ import { MAX_TRACK_HEIGHT, MIN_TRACK_HEIGHT, trackHeightOf } from './constants';
7
8
  import { makeSelectionFramer, structuresSettled } from './frameSelection';
8
9
  import { makeLociChannel } from './lociChannel';
9
10
  import { defaultDisplayName } from './proteinViewSpec';
@@ -80,6 +81,12 @@ function stateModelFactory() {
80
81
  * render the feature/residue tracks at reduced height
81
82
  */
82
83
  compactTracks: true,
84
+ /**
85
+ * #property
86
+ * px height of one feature-track lane once the tracks' resize handle
87
+ * has been dragged; unset, `compactTracks` decides
88
+ */
89
+ trackHeight: types.maybe(types.number),
83
90
  /**
84
91
  * #property
85
92
  * also draw the feature types in MINOR_FEATURE_TYPES and the
@@ -134,6 +141,14 @@ function stateModelFactory() {
134
141
  self.height = n;
135
142
  return n;
136
143
  },
144
+ /**
145
+ * #action
146
+ * Every lane of every feature track grows by the same px, so the track
147
+ * area scales in proportion.
148
+ */
149
+ resizeTracks(distance) {
150
+ self.trackHeight = Math.min(MAX_TRACK_HEIGHT, Math.max(MIN_TRACK_HEIGHT, trackHeightOf(self) + distance));
151
+ },
137
152
  /**
138
153
  * #action
139
154
  */
@@ -241,6 +256,9 @@ function stateModelFactory() {
241
256
  toggleSetting(key) {
242
257
  const value = !self[key];
243
258
  self[key] = value;
259
+ if (key === 'compactTracks') {
260
+ self.trackHeight = undefined;
261
+ }
244
262
  storeSetting(key, value);
245
263
  },
246
264
  /**
@@ -4,7 +4,7 @@ import { autorun, when } from 'mobx';
4
4
  import { alignTranscriptToEntity, alignmentCol, alignmentQuality, chooseMappedEntity, entityLabel, fetchUniProtStructureMappings, fusionPartnerPositions, getPdbIdFromUrl, getUniprotIdFromAlphaFoldTarget, looksLikePlddt, makeCoordinateMapper, makeLabelSeqIdIndex, mappedStructureIdentity, rangeToLabelSeqIds, residueNumber, resolveStructureUrl, stripStopCodon, structureDisplayLabel, structurePos, toLabelSeqIds, unmapStructurePositions, } from 'p2s_mapper';
5
5
  import { alignOffThread, isIdentical } from './alignOffThread';
6
6
  import { connectedHoverTranscriptPos } from './connectedHover';
7
- import { CHAR_WIDTH, COMPACT_TRACK_GAP, COMPACT_TRACK_HEIGHT, MINOR_FEATURE_TYPES, NORMAL_TRACK_GAP, NORMAL_TRACK_HEIGHT, } from './constants';
7
+ import { CHAR_WIDTH, MINOR_FEATURE_TYPES, trackGapOf, trackHeightOf, } from './constants';
8
8
  import { entityAlignedTo } from './entityAlignedTo';
9
9
  import { frameResidues } from './frameSelection';
10
10
  import { clickProteinToGenome, navigateToProteinPosition, structureRangesToGenomeRegions, } from './proteinToGenomeMapping';
@@ -984,14 +984,17 @@ const Structure = types
984
984
  return CHAR_WIDTH;
985
985
  },
986
986
  get trackHeight() {
987
- return this.parentView.compactTracks
988
- ? COMPACT_TRACK_HEIGHT
989
- : NORMAL_TRACK_HEIGHT;
987
+ return trackHeightOf(this.parentView);
990
988
  },
991
989
  get trackGap() {
992
- return this.parentView.compactTracks
993
- ? COMPACT_TRACK_GAP
994
- : NORMAL_TRACK_GAP;
990
+ return trackGapOf(this.trackHeight);
991
+ },
992
+ /**
993
+ * #getter
994
+ * What one lane of the feature tracks takes, bar and gap
995
+ */
996
+ get laneHeight() {
997
+ return this.trackHeight + this.trackGap;
995
998
  },
996
999
  get alignmentAlgorithm() {
997
1000
  return this.parentView.alignmentAlgorithm;
@@ -1160,6 +1163,12 @@ const Structure = types
1160
1163
  setViewError(e) {
1161
1164
  self.parentView.setError(e);
1162
1165
  },
1166
+ /**
1167
+ * #action
1168
+ */
1169
+ resizeTracks(distance) {
1170
+ self.parentView.resizeTracks(distance);
1171
+ },
1163
1172
  /**
1164
1173
  * #action
1165
1174
  */
@@ -0,0 +1,9 @@
1
+ import{Aa as xt,Ba as wt,Ca as we,Da as Pt,E as tt,Ea as Ct,Ha as Tt,Ja as Mt,M as rt,R as ye,U as nt,W as ot,a as M,b as xn,da as at,ea as ut,fa as dt,g as _e,ga as ct,h as wn,ha as pt,ia as Se,k as qe,ka as xe,l as Ke,m as Ge,p as je,pa as mt,q as Ye,r as Qe,t as Xe,ta as ft,u as Ze,ua as gt,v as Re,va as ht,w as et,wa as bt,xa as yt,ya as vt,za as St}from"./chunk-UAUJEOAS.js";import{a as C}from"./chunk-X5RLRQSB.js";import{c as it,d as st,f as ve,g as lt}from"./chunk-GQU3SL56.js";import{a as $}from"./chunk-533QNNGN.js";import{a as ue,b as U}from"./chunk-J236G3A2.js";import{a as x}from"./chunk-GVEKA5YG.js";import{a as Sn}from"./chunk-B3HKXTGK.js";import{a as A,c as l}from"./chunk-KVMUXFPB.js";var Ot=A((ko,Jt)=>{Jt.exports=JBrowseExports["@mui/material/Checkbox"]});var Wt=A((Io,Vt)=>{Vt.exports=JBrowseExports["@mui/material/Divider"]});var Pe=A((Eo,Nt)=>{Nt.exports=JBrowseExports["@mui/material/IconButton"]});var qt=A((Lo,_t)=>{_t.exports=JBrowseExports["@mui/material/ListItemIcon"]});var Gt=A((Bo,Kt)=>{Kt.exports=JBrowseExports["@mui/material/ListItemText"]});var Yt=A((Ao,jt)=>{jt.exports=JBrowseExports["@mui/material/Menu"]});var Xt=A((Do,Qt)=>{Qt.exports=JBrowseExports["@mui/material/MenuItem"]});var Ce=A((Fo,Zt)=>{Zt.exports=JBrowseExports["@mui/material/Tooltip"]});var Te=A((zo,rr)=>{rr.exports=JBrowseExports["@mui/material/Typography"]});var hr=A((jo,gr)=>{gr.exports=JBrowseExports["@mui/material/Chip"]});var yr=A((Yo,br)=>{br.exports=JBrowseExports["@mui/material/Link"]});var w=l(x()),N=l(ue());var kt=l($(),1),X=(0,C.createSvgIcon)((0,kt.jsx)("path",{d:"M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"}),"Close");var yn=l(U()),le=l(M());var E=l(x()),T=l(U());function It(n){let e=n.next();for(;!e.done&&e.value.trim()==="";)e=n.next();return e.value}function Lt(n){let e=n.trim();return e?e.split(/\s+/).every(o=>/^\d+$/.test(o)):!1}function Pn(n){let e=n.trim();return/^\w+\s+/.test(e)&&!Lt(n)}function Cn(n){return n.length>=3&&/^\d+$/.test(n[1])}function Tn(n,e){let o=n.trim().split(/\s+/),r=o[e],i=n.indexOf(r,n.indexOf(o[0])+o[0].length);return[i,i+r.length]}function Et(n){let e=It(n),t=[],o="";if(!e)return;for(;e;)Lt(e)||(Pn(e)?t.push(e):o=e),e=n.next().value;if(t.length===0)return;let r=t.map(P=>P.trim().split(/\s+/)),i=r[0];if(!i)return;let s=Cn(i)?2:1,[a,u]=Tn(t[0],s),h=r.map(P=>P[0]),d=r.map(P=>P[s]),b=o.slice(a,u),v=d[0];if(v){let P=v.length-b.length;P>0&&(b+=" ".repeat(P))}return{ids:h,seqs:d,consensus:b}}function Bt(n){let e,t=Et(n);if(t!==void 0)for(;e=Et(n);){for(let o=0;o<e.seqs.length;o++)t.seqs[o]+=e.seqs[o];t.consensus+=e.consensus}return t}function At(n){let e=n.split(`
2
+ `).filter(s=>!s.startsWith("#")).join(`
3
+ `),t=Bt(e.split(`
4
+ `)[Symbol.iterator]());if(t===void 0)throw new Error("No blocks parsed");let o=t.seqs.map((s,a)=>({id:t.ids[a],seq:s})),{consensus:r}=t,i=o[0];if(!i)throw new Error("No alignments found");if(r.length!==i.seq.length)throw new Error(`Consensus length !== sequence length. Con ${r.length} seq ${i.seq.length}`);if(o.length!==2)throw new Error(`Expected exactly 2 sequences in pairwise alignment, got ${o.length}`);return{consensus:r,alns:o}}var Dt=l(M());var Mn=(0,Dt.observer)(function({model:e}){let[t,o]=(0,E.useState)(""),[r,i]=(0,E.useState)(),{showManualAlignmentDialog:s,alignmentStructure:a}=e,u=()=>{o(""),i(void 0),e.setShowManualAlignmentDialog(!1)},h=()=>{if(t.trim())try{let d=At(t.trim()),b=a?.entities,v=a&&b?Ct(d,a.userProvidedTranscriptSequence,b,a.mappedEntityId):void 0;!a||!v?i("No structure loaded to apply alignment to"):"problem"in v?i(v.problem):(a.importAlignment(Pt(d)),u())}catch(d){i(`Failed to parse alignment: ${d}`)}};return s?E.default.createElement(T.Dialog,{open:!0,onClose:u,maxWidth:"md",fullWidth:!0},E.default.createElement(T.DialogTitle,null,"Import manual alignment"),E.default.createElement(T.DialogContent,null,E.default.createElement(T.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."),E.default.createElement(T.TextField,{multiline:!0,rows:12,fullWidth:!0,placeholder:`Example:
5
+ transcript MKAAYLSMFGKEDHKPFGDDEVELFRAVPGLKLKIAG
6
+ |||||||||||||||||||||||||||||||||||||
7
+ structure MKAAYLSMFGKEDHKPFGDDEVELFRAVPGLKLKIAG`,value:t,onChange:d=>{o(d.target.value),i(void 0)},slotProps:{htmlInput:{style:{fontFamily:"monospace",fontSize:12}}}}),r?E.default.createElement(T.Typography,{color:"error",variant:"body2",sx:{mt:1}},r):null),E.default.createElement(T.DialogActions,null,E.default.createElement(T.Button,{onClick:()=>{u()}},"Cancel"),E.default.createElement(T.Button,{onClick:()=>{h()},variant:"contained",color:"primary",disabled:!t.trim()},"Apply alignment"))):null}),Ft=Mn;var g=l(x()),dn=l(ue());var Ht=l($(),1),$t=(0,C.createSvgIcon)((0,Ht.jsx)("path",{d:"M12 2C6.49 2 2 6.49 2 12s4.49 10 10 10c1.38 0 2.5-1.12 2.5-2.5 0-.61-.23-1.2-.64-1.67-.08-.1-.13-.21-.13-.33 0-.28.22-.5.5-.5H16c3.31 0 6-2.69 6-6 0-4.96-4.49-9-10-9m5.5 11c-.83 0-1.5-.67-1.5-1.5s.67-1.5 1.5-1.5 1.5.67 1.5 1.5-.67 1.5-1.5 1.5m-3-4c-.83 0-1.5-.67-1.5-1.5S13.67 6 14.5 6s1.5.67 1.5 1.5S15.33 9 14.5 9M5 11.5c0-.83.67-1.5 1.5-1.5s1.5.67 1.5 1.5S7.33 13 6.5 13 5 12.33 5 11.5m6-4c0 .83-.67 1.5-1.5 1.5S8 8.33 8 7.5 8.67 6 9.5 6s1.5.67 1.5 1.5"}),"Palette");var Ut=l($(),1),zt=(0,C.createSvgIcon)((0,Ut.jsx)("path",{d:"M3 17v2h6v-2zM3 5v2h10V5zm10 16v-2h8v-2h-8v-2h-2v6zM7 9v2H3v2h4v2h2V9zm14 4v-2H11v2zm-6-4h2V7h4V5h-4V3h-2z"}),"Tune");var cn=l(Ot()),pn=l(Wt()),$e=l(Pe()),mn=l(qt()),fn=l(Gt()),Ue=l(Yt()),ze=l(Xt()),Je=l(Ce()),fe=l(M());var p=l(x()),Rt=l(ue()),m=l(U()),er=l(M());var kn=(0,er.observer)(function({model:e}){let[t,o]=(0,p.useState)(),[r,i]=(0,p.useState)(""),[s,a]=(0,p.useState)(""),[u,h]=(0,p.useState)("pdb"),[d,b]=(0,p.useState)(""),[v,P]=(0,p.useState)(),[_,ee]=(0,p.useState)(!0),{showAddStructureDialog:te}=e,Y=!!e.primaryStructure?.userProvidedTranscriptSequence,q=()=>{o(void 0),i(""),a(""),b(""),P(void 0),e.setShowAddStructureDialog(!1)},O=async()=>{try{let S=u==="pdb"&&r?{pdbId:r}:u==="uniprot"&&s?{uniprotId:s}:u==="url"&&d?{url:d}:u==="file"&&t?{data:await ct(t)}:void 0;if(S){let F=e.primaryStructure,re=_&&F?.userProvidedTranscriptSequence?{userProvidedTranscriptSequence:F.userProvidedTranscriptSequence,feature:F.feature,connectedViewId:F.connectedViewId}:{};e.addStructure({...S,...re}),q()}}catch(S){console.error(S),P(S)}};if(!te)return null;let K=r!==""&&!ye(r),B=u==="url"&&d!==""||u==="file"&&t!==void 0||u==="pdb"&&ye(r)||u==="uniprot"&&s!=="";return p.default.createElement(m.Dialog,{open:!0,onClose:q,maxWidth:"sm",fullWidth:!0},p.default.createElement(m.DialogTitle,null,"Add structure"),p.default.createElement(m.DialogContent,null,v?p.default.createElement(Rt.ErrorMessage,{error:v}):null,p.default.createElement(m.Typography,{variant:"body2",color:"text.secondary",sx:{mb:2}},"Add another structure to superpose on the existing structure(s)."),p.default.createElement(m.FormControl,{component:"fieldset",sx:{mb:2}},p.default.createElement(m.RadioGroup,{value:u,onChange:S=>{h(S.target.value)}},p.default.createElement(m.FormControlLabel,{value:"pdb",control:p.default.createElement(m.Radio,null),label:"PDB ID"}),p.default.createElement(m.FormControlLabel,{value:"uniprot",control:p.default.createElement(m.Radio,null),label:"UniProt ID (AlphaFold)"}),p.default.createElement(m.FormControlLabel,{value:"url",control:p.default.createElement(m.Radio,null),label:"URL"}),p.default.createElement(m.FormControlLabel,{value:"file",control:p.default.createElement(m.Radio,null),label:"File"}))),u==="pdb"?p.default.createElement(m.TextField,{fullWidth:!0,value:r,onChange:S=>{i(S.target.value.trim().toUpperCase())},label:"PDB ID (e.g. 1CRN)",placeholder:"Enter PDB ID",error:K,helperText:K?"A PDB ID is four characters beginning with a digit":void 0,sx:{mb:2}}):null,u==="uniprot"?p.default.createElement(m.TextField,{fullWidth:!0,value:s,onChange:S=>{a(S.target.value.trim().toUpperCase())},label:"UniProt ID (e.g. P04637)",placeholder:"Enter UniProt ID",helperText:"Fetches the AlphaFold predicted structure",sx:{mb:2}}):null,u==="url"?p.default.createElement(m.TextField,{fullWidth:!0,label:"Structure URL",value:d,onChange:S=>{b(S.target.value.trim())},placeholder:"https://files.rcsb.org/download/1CRN.cif",sx:{mb:2}}):null,u==="file"?p.default.createElement("div",{style:{marginBottom:16}},p.default.createElement(m.Button,{variant:"outlined",component:"label"},t?t.name:"Choose file",p.default.createElement("input",{type:"file",hidden:!0,accept:pt,onChange:({target:S})=>{let F=S.files?.[0];F&&o(F)}})),t?p.default.createElement(m.Typography,{variant:"body2",sx:{mt:1}},"Selected: ",t.name):null):null,Y?p.default.createElement(m.FormControlLabel,{control:p.default.createElement(m.Checkbox,{checked:_,onChange:S=>{ee(S.target.checked)}}),label:"Map to the same transcript as the first structure (alignment, hover and click linked to the genome)"}):null,p.default.createElement(m.Typography,{variant:"body2",color:"text.secondary",sx:{mt:1}},"Tip: Structures will be automatically superposed using TM-align. For manual control, use the Mol* controls (\u{1F527} wrench icon).")),p.default.createElement(m.DialogActions,null,p.default.createElement(m.Button,{onClick:()=>{q()}},"Cancel"),p.default.createElement(m.Button,{onClick:()=>{O()},variant:"contained",color:"primary",disabled:!B},"Add structure")))}),tr=kn;var H=l(x()),nr=l(Te()),or={width:8,height:8,border:"1px solid rgba(0,0,0,0.3)"};function ir({title:n,testId:e,color:t,children:o}){return H.default.createElement(nr.default,{variant:"caption",color:t,component:"div","data-testid":e,style:{display:"flex",flexWrap:"wrap",alignItems:"center",gap:6,fontSize:9,paddingLeft:8}},H.default.createElement("span",null,n,":"),o)}function ke({title:n,entries:e,testId:t="track-legend",color:o="textSecondary"}){return H.default.createElement(ir,{title:n,testId:t,color:o},e.map(({label:r,color:i},s)=>H.default.createElement("span",{key:`${s}-${r}`,style:{display:"inline-flex",alignItems:"center",gap:3}},H.default.createElement("span",{style:{...or,background:i}}),r)))}function Ie({title:n,minLabel:e,maxLabel:t,colors:o,testId:r="track-legend",color:i="textSecondary"}){return H.default.createElement(ir,{title:n,testId:r,color:i},e,H.default.createElement("span",{style:{...or,width:60,background:`linear-gradient(to right, ${o.join(", ")})`}}),t)}function Me(n){return`#${n.toString(16).padStart(6,"0")}`}function In(n){return n.replaceAll(/([a-z])([A-Z])(?=[a-z])/g,(e,t,o)=>`${t} ${o.toLowerCase()}`)}function sr({title:n,legend:e}){return e.kind==="table-legend"?H.default.createElement(ke,{title:n,testId:"structure-legend",entries:e.table.map(([t,o])=>({label:In(t),color:Me(o)}))}):H.default.createElement(Ie,{title:n,testId:"structure-legend",minLabel:e.minLabel,maxLabel:e.maxLabel,colors:e.colors.map(t=>Array.isArray(t)?`${Me(t[0])} ${100*t[1]}%`:Me(t))})}var y=l(x());var lr=l($(),1),ar=(0,C.createSvgIcon)((0,lr.jsx)("path",{d:"M10 6 8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z"}),"ChevronRight");var ur=l($(),1),dr=(0,C.createSvgIcon)((0,ur.jsx)("path",{d:"M16.59 8.59 12 13.17 7.41 8.59 6 10l6 6 6-6z"}),"ExpandMore");var cr=l($(),1),pr=(0,C.createSvgIcon)((0,cr.jsx)("path",{d:"M12 4.5C7 4.5 2.73 7.61 1 12c1.73 4.39 6 7.5 11 7.5s9.27-3.11 11-7.5c-1.73-4.39-6-7.5-11-7.5M12 17c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5m0-8c-1.66 0-3 1.34-3 3s1.34 3 3 3 3-1.34 3-3-1.34-3-3-3"}),"Visibility");var mr=l($(),1),fr=(0,C.createSvgIcon)((0,mr.jsx)("path",{d:"M12 7c2.76 0 5 2.24 5 5 0 .65-.13 1.26-.36 1.83l2.92 2.92c1.51-1.26 2.7-2.89 3.43-4.75-1.73-4.39-6-7.5-11-7.5-1.4 0-2.74.25-3.98.7l2.16 2.16C10.74 7.13 11.35 7 12 7M2 4.27l2.28 2.28.46.46C3.08 8.3 1.78 10.02 1 12c1.73 4.39 6 7.5 11 7.5 1.55 0 3.03-.3 4.38-.84l.42.42L19.73 22 21 20.73 3.27 3zM7.53 9.8l1.55 1.55c-.05.21-.08.43-.08.65 0 1.66 1.34 3 3 3 .22 0 .44-.03.65-.08l1.55 1.55c-.67.33-1.41.53-2.2.53-2.76 0-5-2.24-5-5 0-.79.2-1.53.53-2.2m4.31-.78 3.15 3.15.02-.16c0-1.66-1.34-3-3-3z"}),"VisibilityOff");var wr=l(hr()),ce=l(Pe()),Pr=l(yr()),j=l(Ce()),ne=l(Te()),pe=l(M());var de=l(x()),Z=l(U()),vr=l(M());var En=(0,vr.observer)(function({model:e}){let{entities:t,mappedEntity:o,userProvidedTranscriptSequence:r}=e;return!t||t.length<2?null:de.default.createElement(Z.Tooltip,{title:r?"Mapped chain: the one the transcript maps to":"Chain: the one hovers read",placement:"left"},de.default.createElement(Z.TextField,{select:!0,size:"small",variant:"standard","data-testid":"protein-mapped-chain",value:e.pendingEntityId??o?.entityId??"",onChange:i=>{e.chooseEntity(i.target.value)},slotProps:{input:{disableUnderline:!0,sx:{fontSize:12}},htmlInput:{"aria-label":"Mapped chain"}},sx:{minWidth:60}},t.map(i=>de.default.createElement(Z.MenuItem,{key:i.entityId,value:i.entityId,dense:!0},tt(i)))))}),Sr=En;function xr(n){return n.aligned===0?"no residues aligned":[`${Math.round(n.identity*100)}% identity`,n.aligned===n.transcriptLength?"whole transcript":`transcript ${n.transcriptStart}\u2013${n.transcriptEnd} (${n.aligned} of ${n.transcriptLength})`,n.aligned<n.structureLength?`${n.aligned} of ${n.structureLength} structure residues`:void 0].filter(Boolean).join(" \xB7 ")}var Ln=`Under ${Math.round(je*100)}% of the shorter sequence is identical (${Math.round(Qe*100)}% for an alignment of fewer than ${Ye} residues): an alignment this weak is what two unrelated proteins produce, so the positions it maps may be unrelated. Check the mapped chain, the transcript isoform, or import a curated alignment.`,Bn=(0,pe.observer)(function({structure:e}){let{uniprotId:t,uniprotName:o}=e.uniProtEntry;return t?y.default.createElement(j.default,{title:`The mapped chain is UniProt ${t}${o?` (${o})`:""}, which its feature tracks annotate`},y.default.createElement(Pr.default,{variant:"caption",href:ot(t),target:"_blank",rel:"noreferrer",noWrap:!0,sx:{minWidth:40}},"UniProt ",t)):null}),An=(0,pe.observer)(function({model:e,structure:t,readout:o}){let{label:r,alignmentQuality:i,statusMessage:s,hidden:a}=t,u=e.structures.length>1,h=e.showAlignment&&u,d=e.alignmentStructure===t,b=i?et(i):void 0;return y.default.createElement("div",{"data-testid":"structure-row","data-label":r,"data-open":h?d:void 0,"data-hidden":a||void 0,style:{opacity:a?.6:void 0,display:"flex",alignItems:"center",gap:8,minHeight:24,minWidth:0}},h?y.default.createElement(j.default,{title:d?"Alignment shown below":"Show alignment"},y.default.createElement("span",null,y.default.createElement(ce.default,{size:"small","aria-label":`Show ${r} alignment`,"aria-expanded":d,disabled:d,onClick:()=>{e.openAlignmentOf(t)},sx:{p:0}},d?y.default.createElement(dr,{fontSize:"small"}):y.default.createElement(ar,{fontSize:"small"})))):null,y.default.createElement(ne.default,{variant:"caption",sx:{fontWeight:"bold",flexShrink:0}},r),s?y.default.createElement(ne.default,{variant:"caption",color:"error","data-testid":"structure-status"},s):null,o?y.default.createElement(ne.default,{variant:"caption",noWrap:!0,title:o,"data-testid":"structure-hover"},o):i?y.default.createElement(j.default,{title:i.aligned===0?"":`${Re(i)}${b?`, ${b}`:""}; ${Ze(i)}`},y.default.createElement(ne.default,{variant:"caption",color:"textSecondary",noWrap:!0,"data-testid":"header-alignment-quality"},xr(i))):null,y.default.createElement(Bn,{structure:t}),y.default.createElement("div",{style:{flex:1}}),d&&e.showAlignment||!t.userProvidedTranscriptSequence?y.default.createElement(Sr,{model:t}):null,i&&Xe(i)?y.default.createElement(j.default,{title:Ln},y.default.createElement(wr.default,{size:"small",color:"warning",variant:"outlined",label:"low similarity","data-testid":"header-low-similarity"})):null,u||a?y.default.createElement(j.default,{title:a?`Show ${r}`:`Hide ${r}`},y.default.createElement(ce.default,{size:"small","aria-label":a?`Show ${r}`:`Hide ${r}`,"aria-pressed":a,onClick:()=>{t.setHidden(!a)},sx:{p:.25}},a?y.default.createElement(fr,{fontSize:"small"}):y.default.createElement(pr,{fontSize:"small"}))):null,y.default.createElement(j.default,{title:`Remove ${r}`},y.default.createElement(ce.default,{size:"small","aria-label":`Remove ${r}`,onClick:()=>{e.removeStructure(t)},sx:{p:.25}},y.default.createElement(X,{fontSize:"small"}))))}),Dn=(0,pe.observer)(function({model:e}){let{structures:t}=e,o=t.some(r=>r.hoverPosition&&r.hoverPosition.source!=="structure");return y.default.createElement("div",{style:{flex:1,minWidth:0}},t.map((r,i)=>y.default.createElement(An,{key:i,model:e,structure:r,readout:r.hoverString||(o&&r.genomeToTranscriptSeqMapping?"not in structure":"")})))}),Cr=Dn;var f=l(x()),tn=l(ue()),rn=l(U()),nn=l(mt()),on=l(M());var sn=l(xn());var Ee=l(x()),Tr=l(M());function Fn(n,e,t){let o=[];if(!n)return o;for(let r=0;r<e;r++){let i=n[r];if(i===void 0)continue;let s=t(i);s%10===0?o.push({col:r,label:`${s}`}):s%5===0&&o.push({col:r})}return o}var Hn=(0,Tr.observer)(function({model:e,columns:t}){let{columnWidth:o}=e,r=Fn(e.pairwiseAlignmentToStructurePosition,t,i=>e.residueNumber(i));return Ee.default.createElement("div",{style:{position:"relative",height:"100%",color:"#888",fontSize:8}},r.map(({col:i,label:s})=>Ee.default.createElement("span",{key:i,style:{position:"absolute",left:i*o,top:0,bottom:0,borderLeft:"1px solid #aaa",paddingLeft:2,lineHeight:`${12}px`,whiteSpace:"nowrap"}},s)))}),Mr=Hn;var D=l(x()),Le=l(M());function oe({start:n,end:e,columnWidth:t,background:o,border:r}){return D.default.createElement("div",{style:{position:"absolute",top:0,bottom:0,left:n*t,width:(e-n+1)*t,background:o,border:r,boxSizing:"border-box",pointerEvents:"none"}})}function kr({zIndex:n,children:e}){return D.default.createElement("div",{style:{position:"absolute",inset:0,pointerEvents:"none",zIndex:n}},e)}var Ir=(0,Le.observer)(function({model:e,matchHeight:t}){let{clickAlignmentRanges:o,columnWidth:r,showHighlight:i,alignmentMatchSet:s}=e;return D.default.createElement(kr,{zIndex:0},i&&s?D.default.createElement("div",{style:{position:"relative",height:t}},Mt(s).map(a=>D.default.createElement(oe,{key:a.start,start:a.start,end:a.end-1,columnWidth:r,background:yt}))):null,o.map(a=>D.default.createElement(oe,{key:a.start,start:a.start,end:a.end,columnWidth:r,background:ht})))}),$n=(0,Le.observer)(function({model:e}){let{clickAlignmentRanges:t,alignmentHoverRange:o,alignmentHoverPos:r,columnWidth:i}=e;return D.default.createElement(kr,{zIndex:1},t.map(s=>D.default.createElement(oe,{key:s.start,start:s.start,end:s.end,columnWidth:i,background:"transparent",border:bt})),o?D.default.createElement(oe,{start:o.start,end:o.end,columnWidth:i,background:gt}):null,r===void 0?null:D.default.createElement(oe,{start:r,end:r,columnWidth:i,background:ft}))}),Er=$n;var z=l(x());var Lr=l($(),1),Br=(0,C.createSvgIcon)((0,Lr.jsx)("path",{d:"M7.41 18.59 8.83 20 12 16.83 15.17 20l1.41-1.41L12 14zm9.18-13.18L15.17 4 12 7.17 8.83 4 7.41 5.41 12 10z"}),"UnfoldLess");var Ar=l($(),1),Dr=(0,C.createSvgIcon)((0,Ar.jsx)("path",{d:"M12 5.83 15.17 9l1.41-1.41L12 3 7.41 7.59 8.83 9zm0 12.34L8.83 15l-1.41 1.41L12 21l4.59-4.59L15.17 15z"}),"UnfoldMore");var ie=l(U()),Fr=l(M());var zn=(0,Fr.observer)(function({type:e,laneCount:t,model:o}){let r=o.expandedFeatureTypes.has(e),i=Math.min(o.trackHeight,12);return z.default.createElement(ie.Tooltip,{title:e,placement:"left"},z.default.createElement("div",{style:{height:"100%",whiteSpace:"nowrap",display:"flex",alignItems:r?"flex-start":"center",justifyContent:"flex-end",gap:2}},z.default.createElement(ie.IconButton,{onClick:()=>{o.hideFeatureType(e)},title:`Hide ${e} track`,sx:{p:0,color:we}},z.default.createElement(X,{sx:{fontSize:i}})),t>1?z.default.createElement(ie.IconButton,{onClick:()=>{o.toggleFeatureTypeExpanded(e)},title:r?`Collapse ${e} track`:`Expand ${e} track (${t} overlapping rows)`,sx:{p:0,color:we}},r?z.default.createElement(Br,{sx:{fontSize:i}}):z.default.createElement(Dr,{sx:{fontSize:i}})):null,z.default.createElement("span",null,e)))}),Hr=zn;var Ur=l(x()),zr=l(M());function Jn(n,e,t){if(!(!n||!e||n==="-"||e==="-"||n.toUpperCase()===e.toUpperCase()))return t===":"||t==="."?"similar":"different"}function $r(n){let e=n.alns[0]?.seq??"",t=n.alns[1]?.seq??"",o=[];for(let r=0;r<e.length;r++){let i=Jn(e[r],t[r],n.consensus[r]),s=o.at(-1);i&&s?.kind===i&&s.end===r?s.end=r+1:i&&o.push({start:r,end:r+1,kind:i})}return o}var On=(0,zr.observer)(function({model:e}){let{alignment:t,columnWidth:o}=e;return t?$r(t).map(r=>Ur.default.createElement("div",{key:r.start,style:{position:"absolute",top:0,bottom:0,left:r.start*o,width:(r.end-r.start)*o,background:r.kind==="similar"?vt:St}})):null}),Jr=On;var _r=l(x()),qr=l(M());var L=l(x()),Vr=l(U()),Wr=l(M());var Vn={Domain:"#1f77b4","DNA binding":"#ff7f0e",Region:"#2ca02c","Zinc finger":"#d62728","Coiled coil":"#9467bd",Motif:"#8c564b","Compositional bias":"#e377c2",Repeat:"#7f7f7f",Transmembrane:"#bcbd22",Intramembrane:"#17becf","Topological domain":"#aec7e8",Signal:"#ffbb78","Signal peptide":"#ffbb78",Propeptide:"#98df8a","Transit peptide":"#ff9896",Chain:"#c5b0d5","Disulfide bond":"#c49c94","Active site":"#f7b6d2","Binding site":"#c7c7c7",Site:"#dbdb8d","Modified residue":"#9edae5",Glycosylation:"#393b79",Lipidation:"#637939","Cross-link":"#8c6d31","Alternative sequence":"#e7969c","Natural variant":"#de9ed6",Mutagenesis:"#ad494a","Sequence conflict":"#b5cf6b",Helix:"#e7ba52","Beta strand":"#6b6ecf",Turn:"#d6616b","Initiator methionine":"#ce6dbd",Peptide:"#6baed6","Calcium binding":"#fd8d3c","Nucleotide binding":"#74c476"};function Or(n){return Vn[n]??"#999999"}async function Wn(n){let e=await(await rt(n)).text(),t=[];for(let o of e.split(`
8
+ `)){if(o.startsWith("#")||!o.trim())continue;let r=o.split(" ");if(r.length<9)continue;let i=r[2],s=Number.parseInt(r[3]??"",10),a=Number.parseInt(r[4]??"",10),h=(r[8]??"").split(";").find(d=>d.startsWith("Note="))?.slice(5);i&&s>=1&&a>=s&&t.push({type:i,start:s,end:a,description:decodeURIComponent(h??""),uniqueId:`${i}-${s}-${a}-${t.length}`})}return t}function Be(n){let{data:e,error:t,isLoading:o}=at(n?nt(n):null,Wn,ut);return{features:e,error:t,isLoading:o}}function Nn({feature:n,layout:e,model:t}){let o=t.residueNumber(e.structureStart),r=t.residueNumber(e.structureEnd-1),i=o!==n.start||r!==n.end;return L.default.createElement("div",null,L.default.createElement("div",null,L.default.createElement("strong",null,n.type)),L.default.createElement("div",null,"UniProt position: ",n.start,"-",n.end),i?L.default.createElement("div",null,"Structure residue: ",o,"-",r,e.clipped?" (the structure lacks the rest)":""):null,n.description?L.default.createElement("div",null,n.description):null)}var _n=(0,Wr.observer)(function({layout:e,top:t,height:o,selected:r,model:i}){let[s,a]=(0,L.useState)(!1),{feature:u,alignmentStart:h,alignmentEnd:d}=e,{columnWidth:b}=i;return L.default.createElement(Vr.Tooltip,{title:L.default.createElement(Nn,{feature:u,layout:e,model:i}),followCursor:!0},L.default.createElement("div",{"data-testid":`protein-feature-${u.type}`,"data-feature-id":u.uniqueId,"data-feature-start":u.start,"data-feature-end":u.end,onClick:()=>{r?(i.setSelectedFeatureId(void 0),i.setClickedStructureRanges([])):(i.setSelectedFeatureId(u.uniqueId),Tt({model:i,structureSeqPos:e.structureStart,structureSeqEndPos:e.structureEnd}).catch(v=>{console.error(v),i.setViewError(v)}))},onMouseEnter:()=>{a(!0),i.setAlignmentHoverRange({start:h,end:d})},onMouseLeave:()=>{a(!1),i.setAlignmentHoverRange(void 0)},style:{position:"absolute",left:h*b,top:t,width:(d-h+1)*b,height:o,backgroundColor:Or(u.type),opacity:s||r?.9:.6,cursor:"pointer",borderRadius:2,border:r?xt:s?wt:"none",boxSizing:"border-box"}}))}),Nr=_n;var qn=(0,qr.observer)(function({group:e,model:t}){let{selectedFeatureId:o,trackGap:r}=t,i=t.expandedFeatureTypes.has(e.type)?e.laneCount:1,s=`calc(${100/i}% - ${r}px)`;return e.layouts.map(a=>_r.default.createElement(Nr,{key:a.feature.uniqueId,layout:a,top:`${Math.min(a.lane,i-1)/i*100}%`,height:s,selected:o===a.feature.uniqueId,model:t}))}),Kr=qn;var W=l(x()),Gr=l(U()),Ae=l(M()),Kn=(0,Ae.observer)(function({cells:e,colorFor:t,model:o}){let{columnWidth:r,trackGap:i}=o;return W.default.createElement("div",{style:{position:"relative",height:`calc(100% - ${i}px)`}},e.map(s=>W.default.createElement("div",{key:s.col,style:{position:"absolute",left:s.col*r,width:r,height:"100%",backgroundColor:t(s.value)}})))}),Gn=(0,Ae.observer)(function({cells:e,colorFor:t,formatValue:o,model:r}){let i=(0,W.useMemo)(()=>new Map(e.map(u=>[u.col,u.value])),[e]),{alignmentHoverPos:s}=r,a=s===void 0?void 0:i.get(s);return W.default.createElement(Gr.Tooltip,{title:a===void 0?"":o(a),followCursor:!0},W.default.createElement("div",{style:{height:"100%"}},W.default.createElement(Kn,{cells:e,colorFor:t,model:r})))}),De=Gn;var jr=l(x()),Yr=l(M()),jn=(0,Yr.observer)(function({model:e,str:t}){let{columnWidth:o}=e;return t.split("").map((r,i)=>jr.default.createElement("span",{key:i,style:{position:"absolute",left:i*o,width:o}},r===" "?"\xA0":r))}),Qr=jn;var Xr=l(mt());function Yn({x:n,width:e,scrollLeft:t,clientWidth:o}){return n>=t&&n+e<=t+o?void 0:n+e/2-o/2}function Zr({start:n,end:e,scrollLeft:t,clientWidth:o}){let r=t+o;return e>=t&&n<=r?void 0:(n+e)/2-o/2}function Rr(n,e){return(0,Xr.reaction)(()=>n.alignmentHoverPos,t=>{let o=e();if(t!==void 0&&o&&n.autoScrollAlignment&&!n.isMouseInAlignment){let r=Yn({x:t*n.columnWidth,width:n.columnWidth,scrollLeft:o.scrollLeft,clientWidth:o.clientWidth});r!==void 0&&(o.scrollLeft=r)}})}var en=l(x());var Qn=new Set(["Disulfide bond","Cross-link"]);function Xn(n,e,t){let o=h=>{let d=t(h),b=d===void 0?void 0:e[d];return d===void 0||b===void 0?void 0:{structurePos:d,column:b}},r=n.start,i=o(r);for(;!i&&r<n.end;)i=o(++r);let s=n.end,a=o(s);for(;!a&&s>r;)a=o(--s);let u=r!==n.start||s!==n.end;return!i||!a||u&&Qn.has(n.type)?void 0:{feature:n,structureStart:i.structurePos,structureEnd:a.structurePos+1,alignmentStart:i.column,alignmentEnd:a.column,clipped:u,lane:0}}function Zn(n){let e=[],t=[...n].sort((o,r)=>o.alignmentStart-r.alignmentStart);for(let o of t){let r=e.findIndex(i=>i<o.alignmentStart);r===-1?(o.lane=e.length,e.push(o.alignmentEnd)):(o.lane=r,e[r]=o.alignmentEnd)}return Math.max(e.length,1)}function Rn(n){return[...n].sort((e,t)=>t.alignmentEnd-t.alignmentStart-(e.alignmentEnd-e.alignmentStart))}function Fe(n,e,t){let{features:o,isLoading:r,error:i}=Be(e),{omittedFeatureTypes:s,structurePositionToAlignmentMap:a}=n;return{groups:(0,en.useMemo)(()=>{if(!o||!a)return;let h=new Map;for(let d of o)if(!s.has(d.type)){let b=Xn(d,a,t);if(b){let v=h.get(d.type);v?v.push(b):h.set(d.type,[b])}}return[...h].map(([d,b])=>({type:d,laneCount:Zn(b),layouts:Rn(b)}))},[o,s,a,t]),isLoading:r,error:i}}var to=(0,sn.makeStyles)()(n=>({scroll:{overflow:"auto",whiteSpace:"nowrap",flex:1,paddingBottom:2,backgroundColor:n.palette.background.paper},trackMessage:{position:"sticky",left:0,lineHeight:`${12}px`,color:n.palette.text.secondary},trackError:{color:n.palette.error.main}}));function se({label:n,title:e}){return f.default.createElement(rn.Tooltip,{title:e,placement:"left"},f.default.createElement("div",{style:{height:"100%",display:"flex",alignItems:"center",justifyContent:"flex-end"}},n))}var ro=(0,on.observer)(function({model:e}){let{alignment:t,showProteinTracks:o,showAllFeatureTracks:r,label:i,confidenceCells:s,columnWidth:a,laneHeight:u}=e,h=r?e.hydrophobicityCells:[],{classes:d,cx:b}=to(),v=(0,f.useRef)(null),P=(0,f.useRef)(void 0),{uniprotId:_,mapUniProtPosition:ee,isLoading:te,error:Y}=e.uniProtEntry,{groups:q,isLoading:O,error:K}=Fe(e,_,ee),B=te||O,S=Y??K,F=S?`${Y?`Could not map ${i} to a UniProt entry through SIFTS`:`Could not load UniProt features for ${_??i}`}: ${_e(S)}`:void 0;if((0,f.useEffect)(()=>Rr(e,()=>v.current),[e]),(0,f.useEffect)(()=>(0,nn.autorun)(()=>{let c=v.current,k=e.clickAlignmentRanges,I=k[0];if(c)if(I){let Q=k.map(G=>`${G.start}-${G.end}`).join(",");if(Q!==P.current){P.current=Q;let G=Zr({start:I.start*e.columnWidth,end:(I.end+1)*e.columnWidth,scrollLeft:c.scrollLeft,clientWidth:c.clientWidth});G!==void 0&&(c.scrollLeft=G)}}else P.current=void 0}),[e]),!t)return null;let re=Ge(t),Ve=(c,k,I,Q,G)=>({key:c,height:12,label:f.default.createElement(se,{label:k,title:I}),content:f.default.createElement("div",{style:{lineHeight:`${12}px`}},G,f.default.createElement(Qr,{model:e,str:Q})),selectsResidue:!0}),ge=F??(B?"Loading UniProt features...":""),We=[Ve("transcript","GENOME","The protein as the reference genome's transcript translates",qe(t)),Ve("structure","STRUCT","The protein as the structure file spells it. Shaded where it differs from the transcript's: amber for a similar amino acid, red for a different one.",Ke(t),f.default.createElement(Jr,{model:e}))],ae=[...We,{key:"ruler",height:12,label:f.default.createElement(se,{label:"residue",title:"Residue numbers as the structure's authors assigned them, the numbering papers and the 3D view's hover label use"}),content:f.default.createElement(Mr,{model:e,columns:re}),selectsResidue:!0}],vn=We.length*12,he=0,be=(c,k,I,Q)=>{he+=k,ae.push({key:c,height:k*u,label:I,content:Q})};if(o){ge&&ae.push({key:"uniprot-status",height:12,label:f.default.createElement(se,{label:"UniProt",title:ge}),content:f.default.createElement("span",{className:b(d.trackMessage,F&&d.trackError)},ge)});for(let c of q??[])be(`feature-${c.type}`,e.expandedFeatureTypes.has(c.type)?c.laneCount:1,f.default.createElement(Hr,{type:c.type,laneCount:c.laneCount,model:e}),f.default.createElement(Kr,{group:c,model:e}));s.length>0&&be("plddt",1,f.default.createElement(se,{label:"pLDDT",title:f.default.createElement(ke,{title:"AlphaFold per-residue confidence (pLDDT)",entries:it,color:"inherit"})}),f.default.createElement(De,{cells:s,colorFor:st,formatValue:c=>`pLDDT ${c.toFixed(0)}`,model:e})),h.length>0&&be("hydrophobicity",1,f.default.createElement(se,{label:"hydro",title:f.default.createElement(Ie,{title:"Kyte-Doolittle hydrophobicity",testId:"hydrophobicity-legend",minLabel:"hydrophilic",maxLabel:"hydrophobic",colors:lt.map(c=>ve(c)),color:"inherit"})}),f.default.createElement(De,{cells:h,colorFor:ve,formatValue:c=>`Kyte-Doolittle ${c.toFixed(1)}`,model:e}))}let Ne=c=>{let{left:k}=c.currentTarget.getBoundingClientRect(),I=Math.floor((c.clientX-k)/a);return I>=0&&I<re?I:void 0};return f.default.createElement("div",{"data-testid":"protein-alignment-panel","data-structure":i},f.default.createElement("div",{style:{display:"flex",fontSize:9,fontFamily:"monospace",margin:"2px 8px 4px"},onMouseEnter:()=>{e.setIsMouseInAlignment(!0)},onMouseLeave:()=>{e.leaveAlignment()}},f.default.createElement("div",{style:{flexShrink:0,minWidth:124,paddingRight:4,whiteSpace:"nowrap",lineHeight:1}},ae.map(c=>f.default.createElement("div",{key:c.key,"data-row-label":c.key,style:{height:c.height,overflow:"hidden"}},c.label))),f.default.createElement("div",{ref:v,className:d.scroll},f.default.createElement("div",{"data-testid":"alignment-rows",style:{position:"relative",width:re*a},onMouseMove:c=>{let k=Ne(c);k===void 0?e.setHoveredPosition(void 0):e.hoverAlignmentPosition(k)},onMouseLeave:()=>{e.setHoveredPosition(void 0)}},f.default.createElement(Ir,{model:e,matchHeight:vn}),ae.map(c=>f.default.createElement("div",{key:c.key,"data-row":c.key,style:{position:"relative",height:c.height,cursor:c.selectsResidue?"pointer":void 0},onClick:c.selectsResidue?k=>{let I=Ne(k);I!==void 0&&e.clickAlignmentPosition(I)}:void 0},c.content)),f.default.createElement(Er,{model:e})))),he>0?f.default.createElement(tn.ResizeHandle,{bar:!0,title:"Drag to resize the feature tracks","data-testid":"feature-tracks-resize-handle",gain:he,onDrag:c=>{e.resizeTracks(c)}}):null)}),ln=ro;var R=l(x()),an=l(Sn());var me=l(U()),no=(0,R.lazy)(()=>import("./ProteinAlignmentHelpDialog-6VFZFN6X.js"));function He({model:n}){return R.default.createElement(me.Tooltip,{title:"What the alignment panel shows"},R.default.createElement(me.IconButton,{size:"small",onClick:()=>{(0,an.getSession)(n).queueDialog(e=>[no,{handleClose:e}])}},R.default.createElement(dt,{fontSize:"small"})))}var oo=(0,fe.observer)(function({model:e}){let[t,o]=(0,g.useState)(null),i=`Color scheme: ${xe.find(s=>s.value===e.colorScheme)?.label??e.colorScheme}`;return g.default.createElement(g.default.Fragment,null,g.default.createElement(Je.default,{title:i},g.default.createElement($e.default,{size:"small","aria-label":i,onClick:s=>{o(s.currentTarget)}},g.default.createElement($t,{fontSize:"small"}))),g.default.createElement(Ue.default,{anchorEl:t,open:!!t,onClose:()=>{o(null)}},xe.map(s=>g.default.createElement(ze.default,{key:s.value,dense:!0,selected:s.value===e.colorScheme,onClick:()=>{e.setColorScheme(s.value),o(null)}},s.label))))});function un({checked:n,label:e,onToggle:t}){return g.default.createElement(ze.default,{onClick:()=>{t()},dense:!0},g.default.createElement(mn.default,null,g.default.createElement(cn.default,{checked:n,size:"small",edge:"start",disableRipple:!0})),g.default.createElement(fn.default,null,e))}var io=(0,fe.observer)(function({model:e}){let[t,o]=(0,g.useState)(null);return g.default.createElement(g.default.Fragment,null,g.default.createElement(Je.default,{title:"Display settings"},g.default.createElement($e.default,{size:"small",onClick:r=>{o(r.currentTarget)}},g.default.createElement(zt,{fontSize:"small"}))),g.default.createElement(Ue.default,{anchorEl:t,open:!!t,onClose:()=>{o(null)}},e.displayToggles.map(r=>g.default.createElement(un,{key:r.label,checked:r.checked,label:r.label,onToggle:r.toggle})),g.default.createElement(pn.default,null),e.behaviorToggles.map(r=>g.default.createElement(un,{key:r.label,checked:r.checked,label:r.label,onToggle:r.toggle}))))}),so=(0,fe.observer)(function({model:e}){let{alignmentStructure:t,showAlignment:o,colorLegend:r}=e;return g.default.createElement("div",null,g.default.createElement("div",{style:{display:"flex",alignItems:"flex-start",gap:8}},g.default.createElement(Cr,{model:e}),g.default.createElement("div",{style:{display:"flex",gap:4,alignItems:"center",flexShrink:0,minHeight:24}},g.default.createElement(oo,{model:e}),g.default.createElement(io,{model:e}),g.default.createElement(He,{model:e}))),r?g.default.createElement(sr,{title:"Structure colors",legend:r}):null,o&&t?.pairwiseAlignment?g.default.createElement(ln,{model:t}):o&&t?.alignmentPending?g.default.createElement(dn.LoadingEllipses,{message:"Loading pairwise alignment"}):null,g.default.createElement(tr,{model:e}))}),gn=so;var J=l(x()),bn=l(wn());var hn=!1;function lo(n){if(!hn){hn=!0;let e=document.createElement("style");e.append(n),document.head.append(e)}}function Oe({showControls:n,model:e}){let t=(0,J.useRef)(null),o=(0,J.useRef)(null),[r,i]=(0,J.useState)(),[s,a]=(0,J.useState)(!0);return(0,J.useEffect)(()=>{let u={cancelled:!1};return(async()=>{try{if(!t.current)return;let{Color:h,GeometryExport:d,MAQualityAssessment:b,PluginConfig:v,PluginSpec:P,DefaultPluginUISpec:_,createPluginUI:ee,registerColorThemes:te,renderReact18:Y,css:q}=await Se();lo(q);let O=document.createElement("div");t.current.append(O),u.host=O;let K=_(),B=await ee({target:O,render:Y,spec:{...K,behaviors:[...K.behaviors,P.Behavior(d),P.Behavior(b)],layout:{initial:{controlsDisplay:"reactive",showControls:n}},config:[[v.Viewport.ShowExpand,!1]]}});await B.initialized,te(B),B.canvas3d?.setProps({renderer:{selectColor:h(16711935),selectStrength:1},marking:{selectEdgeColor:h(16711935)}}),u.cancelled?(B.dispose(),O.remove()):(u.plugin=B,o.current=B,e?.setMolstarPluginContext(B))}catch(h){console.error(h),i(h)}finally{a(!1)}})(),()=>{u.cancelled=!0,o.current=null,e&&(0,bn.isAlive)(e)&&e.setMolstarPluginContext(void 0),u.plugin?.dispose(),u.host?.remove()}},[]),(0,J.useEffect)(()=>{let u={cancelled:!1};return(async()=>{let h=o.current;if(h){let{PluginCommands:d}=await Se();u.cancelled||await d.Layout.Update(h,{state:{showControls:n}})}})(),()=>{u.cancelled=!0}},[n]),{parentRef:t,error:r,loading:s}}var ao=(0,le.observer)(function({model:e}){let{showControls:t}=e,{parentRef:o,error:r,loading:i}=Oe({showControls:t,model:e});return r?w.default.createElement(N.ErrorMessage,{error:r}):w.default.createElement(po,{model:e,parentRef:o,loading:i})}),uo=(0,le.observer)(function({model:e}){let{error:t}=e;return t?w.default.createElement("div",{style:{display:"flex",alignItems:"flex-start"}},w.default.createElement("div",{style:{flex:1}},w.default.createElement(N.ErrorMessage,{error:t})),w.default.createElement(yn.IconButton,{size:"small","aria-label":"Dismiss error",onClick:()=>{e.setError(void 0)}},w.default.createElement(X,{fontSize:"small"}))):null}),co=(0,le.observer)(function({model:e}){let{loadingMessages:t}=e;return t.length>0?w.default.createElement("div",{"data-testid":"protein-view-loading-overlay",style:{position:"absolute",top:8,left:8,maxWidth:"calc(100% - 16px)",padding:"2px 8px",borderRadius:4,background:"rgba(255,255,255,0.85)",color:"#000",pointerEvents:"none"}},t.map(({id:o,message:r})=>w.default.createElement(N.LoadingEllipses,{key:o,message:r}))):null}),po=(0,le.observer)(function({model:e,parentRef:t,loading:o}){let{width:r,height:i}=e,s=!o&&!e.showLoading;return w.default.createElement("div",{style:{background:"#ccc"},"data-testid":s?"protein-view-ready":"protein-view-loading"},w.default.createElement(uo,{model:e}),o?w.default.createElement(N.LoadingEllipses,{message:"Loading protein viewer"}):w.default.createElement(gn,{model:e}),w.default.createElement("div",{style:{position:"relative",width:r,height:i}},w.default.createElement("div",{ref:t,"data-testid":"protein-view-molstar",style:{position:"relative",width:r,height:i}}),w.default.createElement(co,{model:e})),w.default.createElement(N.ResizeHandle,{style:{height:4,background:"grey"},onDrag:a=>e.setHeight(e.height+a)}),w.default.createElement(Ft,{model:e}))}),vs=ao;export{vs as default};
9
+ //# sourceMappingURL=ProteinView-JMWV7XDK.js.map