protvista-uniprot 4.3.7 → 4.4.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.
@@ -1,12 +1,13 @@
1
1
  import { LitElement, TemplateResult } from 'lit';
2
2
  type ProcessedStructureData = {
3
3
  id: string;
4
- source: 'PDB' | 'AlphaFold';
5
- method: string;
4
+ source: string;
5
+ method?: string;
6
6
  resolution?: string;
7
7
  chain?: string;
8
8
  positions?: string;
9
9
  downloadLink?: string;
10
+ sourceDBLink?: string;
10
11
  protvistaFeatureId: string;
11
12
  };
12
13
  declare class ProtvistaUniprotStructure extends LitElement {
@@ -17,6 +18,7 @@ declare class ProtvistaUniprotStructure extends LitElement {
17
18
  colorTheme?: string;
18
19
  private loading?;
19
20
  private alphamissenseAvailable?;
21
+ private modelUrl;
20
22
  constructor();
21
23
  static get properties(): {
22
24
  accession: {
@@ -43,8 +45,10 @@ declare class ProtvistaUniprotStructure extends LitElement {
43
45
  updated(): void;
44
46
  addStyles(): void;
45
47
  removeStyles(): void;
46
- onTableRowClick({ id }: {
48
+ onTableRowClick({ id, source, downloadLink, }: {
47
49
  id: string;
50
+ source?: string;
51
+ downloadLink?: string;
48
52
  }): void;
49
53
  get cssStyle(): import('lit').CSSResult;
50
54
  /**
@@ -1,7 +1,5 @@
1
1
  import { PTM } from '../adapters/ptm-exchange-adapter';
2
2
  export declare const phosphorylate: (aa: string) => string;
3
3
  export declare const sumoylate: (aa: string) => string;
4
- export declare const ubiquitinate: (aa: string) => string;
5
- export declare const acetylate: (aa: string) => string;
6
4
  declare const formatTooltip: (title: string, ptms: PTM[], aa: string, confidenceScore: string) => string;
7
5
  export default formatTooltip;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "protvista-uniprot",
3
3
  "description": "ProtVista tool for the UniProt website",
4
- "version": "4.3.7",
4
+ "version": "4.4.0",
5
5
  "files": [
6
6
  "dist",
7
7
  "src"
@@ -36,8 +36,8 @@
36
36
  "@nightingale-elements/nightingale-manager": "5.6.0",
37
37
  "@nightingale-elements/nightingale-navigation": "5.6.0",
38
38
  "@nightingale-elements/nightingale-sequence": "5.6.0",
39
- "@nightingale-elements/nightingale-sequence-heatmap": "5.6.2",
40
- "@nightingale-elements/nightingale-structure": "5.6.2",
39
+ "@nightingale-elements/nightingale-sequence-heatmap": "5.6.0",
40
+ "@nightingale-elements/nightingale-structure": "5.7.0",
41
41
  "@nightingale-elements/nightingale-track-canvas": "5.6.0",
42
42
  "@nightingale-elements/nightingale-variation": "5.6.0",
43
43
  "color-hash": "2.0.2",
@@ -1,38 +1,15 @@
1
1
  import { AlphafoldPayload } from './types/alphafold';
2
2
 
3
- // from color scale B:0,H:0.1132,V:0.2264,L:0.3395,A:0.4527,l:0.5895,h:0.7264,p:0.8632,P:1
4
- const certainlyBenign = 0;
5
- const benign = 0.1132;
6
- const veryLikelyBenign = 0.2264;
7
- const likelyBenign = 0.3395;
8
- const ambiguous = 0.4527;
9
- const likelyAmbiguous = 0.5895;
10
- const likelyPathogenic = 0.7264;
11
- const pathogenic = 0.8632;
12
- const certainlyPathogenic = 1;
3
+ // from example data
4
+ // benign: [0.0448,0.3397]: x < 0.34
5
+ // ambiguous: [0.34,0.564]: 0.34 <= x <= 0.564
6
+ // pathogenic: [0.5646,0.9999]: 0.564 < x
7
+ const benign = 0.34;
8
+ const pathogenic = 0.564;
13
9
 
14
10
  export const rowSplitter = /\s*\n\s*/;
15
11
  export const cellSplitter = /^(.)(\d+)(.),(.+),(\w+)$/;
16
12
 
17
- const pathogenicityCategories = [
18
- { min: certainlyBenign, max: benign, code: 'H' },
19
- { min: benign, max: veryLikelyBenign, code: 'V' },
20
- { min: veryLikelyBenign, max: likelyBenign, code: 'L' },
21
- { min: likelyBenign, max: ambiguous, code: 'A' },
22
- { min: ambiguous, max: likelyAmbiguous, code: 'l' },
23
- { min: likelyAmbiguous, max: likelyPathogenic, code: 'h' },
24
- { min: likelyPathogenic, max: pathogenic, code: 'p' },
25
- { min: pathogenic, max: certainlyPathogenic, code: 'P' },
26
- ];
27
-
28
- const getPathogenicityCode = (score) => {
29
- for (const { min, max, code } of pathogenicityCategories) {
30
- if (score >= min && score < max) {
31
- return code;
32
- }
33
- }
34
- };
35
-
36
13
  type Row = {
37
14
  wildType: string;
38
15
  position: number;
@@ -71,6 +48,7 @@ const parseCSV = (rawText: string): string => {
71
48
 
72
49
  const out = [];
73
50
  for (const position of positions) {
51
+ let letter = 'A';
74
52
  // maximum
75
53
  // const value = Math.max(
76
54
  // ...position.map((variation) => variation.pathogenicityScore)
@@ -81,7 +59,11 @@ const parseCSV = (rawText: string): string => {
81
59
  (acc, variation) => acc + +variation.pathogenicityScore,
82
60
  0
83
61
  ) / position.length;
84
- const letter = getPathogenicityCode(value);
62
+ if (value > pathogenic) {
63
+ letter = 'P';
64
+ } else if (value < benign) {
65
+ letter = 'B';
66
+ }
85
67
  out.push(letter);
86
68
  }
87
69
 
@@ -13,19 +13,64 @@ const transformData = (data) => {
13
13
  let adaptedData = [];
14
14
 
15
15
  if (data && data.length !== 0) {
16
- adaptedData = data.features.map((feature) => {
17
- feature.residuesToHighlight = feature.ptms?.map((ptm) => ({
18
- name: ptm.name,
19
- position: ptm.position,
20
- sources: ptm.sources,
21
- dbReferences: ptm.dbReferences,
22
- }));
23
- return Object.assign(
24
- feature,
25
- proteomicsTrackProperties(feature, data.taxid)
26
- );
16
+ /* Important: The PTM map is a temporary patch until multiple modifications are shown in the peptide. At this point, only 'phospho' sites are of interest.
17
+ Once they are available in the data, there is no need for the below merging */
18
+
19
+ // To merge PTM data present in same residue in a same length peptide, have a map [key: start-end-phospho site 1-... phosphosite n, value: corresponding feature elements]
20
+ const ptmMap: Record<string, any> = {};
21
+ data.features.forEach((feature) => {
22
+ let ft = `${feature.begin}-${feature.end}`;
23
+ if (feature.ptms) {
24
+ feature.ptms.forEach((ptm) => {
25
+ ft += `-${ptm.position}`;
26
+ });
27
+ ptmMap[ft] = ft in ptmMap ? [...ptmMap[ft], feature] : [feature];
28
+ }
27
29
  });
28
30
 
31
+ // The else part alone is enough if the PTM information need not be merged.
32
+ if (Object.keys(ptmMap).length) {
33
+ adaptedData = Object.values(ptmMap).map((features) => {
34
+ // Only the dbReferences have to be merged as the rest is all the same
35
+ const mergedDbReferences = [];
36
+ features.forEach((feature) => {
37
+ feature.ptms.forEach((ptm) => {
38
+ ptm.dbReferences.forEach((dbReference) => {
39
+ mergedDbReferences.push(dbReference);
40
+ });
41
+ });
42
+ });
43
+
44
+ const mergedFeatures = {
45
+ type: features[0].type,
46
+ begin: features[0].begin,
47
+ end: features[0].end,
48
+ xrefs: features[0].xrefs,
49
+ evidences: features[0].evidences,
50
+ peptide: features[0].peptide,
51
+ unique: features[0].unique,
52
+ residuesToHighlight: features[0].ptms.map((ptm) => ({
53
+ name: ptm.name,
54
+ position: ptm.position,
55
+ sources: ptm.sources,
56
+ dbReferences: mergedDbReferences,
57
+ })),
58
+ };
59
+
60
+ return Object.assign(
61
+ mergedFeatures,
62
+ proteomicsTrackProperties(mergedFeatures, data.taxid)
63
+ );
64
+ }, []);
65
+ } else {
66
+ adaptedData = data.features.map((feature) => {
67
+ return Object.assign(
68
+ feature,
69
+ proteomicsTrackProperties(feature, data.taxid)
70
+ );
71
+ });
72
+ }
73
+
29
74
  adaptedData = renameProperties(adaptedData);
30
75
  }
31
76
  return adaptedData;
@@ -59,50 +59,38 @@ const convertPtmExchangePtms = (
59
59
  aa: string,
60
60
  absolutePosition: number
61
61
  ) => {
62
- const groupPtmsByModification: Record<string, PTM[]> = {};
63
- for (const ptm of ptms) {
64
- if (groupPtmsByModification[ptm.name]) {
65
- groupPtmsByModification[ptm.name].push(ptm);
66
- } else {
67
- groupPtmsByModification[ptm.name] = [ptm];
68
- }
69
- }
70
-
71
- return Object.values(groupPtmsByModification).map((groupedPtms) => {
72
- const confidenceScores = new Set(
73
- groupedPtms.flatMap(({ dbReferences }) =>
74
- dbReferences?.map(({ properties }) => properties['Confidence score'])
75
- )
62
+ const confidenceScores = new Set(
63
+ ptms.flatMap(({ dbReferences }) =>
64
+ dbReferences?.map(({ properties }) => properties['Confidence score'])
65
+ )
66
+ );
67
+ let confidenceScore: string | null = null;
68
+
69
+ if (!confidenceScores.size) {
70
+ console.log('PTM has no confidence score');
71
+ } else if (confidenceScores.size > 1) {
72
+ console.error(
73
+ `PTM has a mixture of confidence scores: ${Array.from(confidenceScores)}`
76
74
  );
77
- let confidenceScore: string | null = null;
78
- if (confidenceScores.size) {
79
- if (confidenceScores.size > 1) {
80
- console.error(
81
- `PTMeXchange PTM has a mixture of confidence scores: ${Array.from(
82
- confidenceScores
83
- )}`
84
- );
85
- } else {
86
- [confidenceScore] = confidenceScores;
87
- }
88
- }
75
+ } else {
76
+ [confidenceScore] = confidenceScores;
77
+ }
89
78
 
90
- return {
91
- source: 'PTMeXchange',
92
- type: 'MOD_RES_LS',
93
- start: absolutePosition,
94
- end: absolutePosition,
95
- shape: 'triangle',
96
- tooltipContent: formatTooltip(
97
- `MOD_RES_LS ${absolutePosition}-${absolutePosition}`,
98
- groupedPtms,
99
- aa,
100
- confidenceScore
101
- ),
102
- color:
103
- (confidenceScore && ConfidenceScoreColors[confidenceScore]) || 'black',
104
- };
105
- });
79
+ return {
80
+ source: 'PTMeXchange',
81
+ type: 'MOD_RES_LS',
82
+ start: absolutePosition,
83
+ end: absolutePosition,
84
+ shape: 'triangle',
85
+ tooltipContent: formatTooltip(
86
+ `MOD_RES_LS ${absolutePosition}-${absolutePosition}`,
87
+ ptms,
88
+ aa,
89
+ confidenceScore
90
+ ),
91
+ color:
92
+ (confidenceScore && ConfidenceScoreColors[confidenceScore]) || 'black',
93
+ };
106
94
  };
107
95
 
108
96
  const transformData = (data: ProteomicsPtm) => {
@@ -142,7 +130,7 @@ const transformData = (data: ProteomicsPtm) => {
142
130
  return Object.entries(absolutePositionToPtms).map(
143
131
  ([absolutePosition, { ptms, aa }]) =>
144
132
  convertPtmExchangePtms(ptms, aa, +absolutePosition)
145
- ).flat();
133
+ );
146
134
  }
147
135
  }
148
136
  return [];
package/src/config.ts CHANGED
@@ -815,8 +815,8 @@ const config: ProtvistaConfig = {
815
815
  name: 'ALPHAMISSENSE_PATHOGENICITY',
816
816
  label: 'AlphaMissense',
817
817
  trackType: 'nightingale-colored-sequence',
818
- scale: 'B:0,H:0.1132,V:0.2264,L:0.3395,A:0.4527,l:0.5895,h:0.7264,p:0.8632,P:1',
819
- 'color-range': '#2166ac:0,#4290bf:0.1132,#8cbcd4:0.2264,#c3d6e0:0.3395,#e2e2e2:0.4527,#edcdba:0.5895,#e99e7c:0.7264,#d15e4b:0.8632,#b2182b:1',
818
+ scale: 'P:100,A:50,B:0',
819
+ 'color-range': '#9a131a:100,#a8a9ad:50,#3d5493:0',
820
820
  tracks: [
821
821
  {
822
822
  name: 'alphamissense_pathogenicity',
@@ -851,67 +851,6 @@ const config: ProtvistaConfig = {
851
851
  },
852
852
  ],
853
853
  },
854
- {
855
- name: 'STRUCTURAL',
856
- label: 'Structural features',
857
- trackType: 'nightingale-track-canvas',
858
- tracks: [
859
- {
860
- name: 'helix',
861
- label: 'Helix',
862
- filter: 'HELIX',
863
- trackType: 'nightingale-track-canvas',
864
- data: [
865
- {
866
- adapter: 'feature-adapter',
867
- url: `${proteinsApiServices.features}{accession}`,
868
- },
869
- ],
870
- tooltip: 'The positions of experimentally determined helical regions',
871
- },
872
- {
873
- name: 'strand',
874
- label: 'Beta strand',
875
- filter: 'STRAND',
876
- trackType: 'nightingale-track-canvas',
877
- data: [
878
- {
879
- adapter: 'feature-adapter',
880
- url: `${proteinsApiServices.features}{accession}`,
881
- },
882
- ],
883
- tooltip: 'The positions of experimentally determined beta strands',
884
- },
885
- {
886
- name: 'turn',
887
- label: 'Turn',
888
- filter: 'TURN',
889
- trackType: 'nightingale-track-canvas',
890
- data: [
891
- {
892
- adapter: 'feature-adapter',
893
- url: `${proteinsApiServices.features}{accession}`,
894
- },
895
- ],
896
- tooltip:
897
- 'The positions of experimentally determined hydrogen-bonded turns',
898
- },
899
- {
900
- name: 'coiled',
901
- label: 'Coiled coil',
902
- filter: 'COILED',
903
- trackType: 'nightingale-track-canvas',
904
- data: [
905
- {
906
- adapter: 'feature-adapter',
907
- url: `${proteinsApiServices.features}{accession}`,
908
- },
909
- ],
910
- tooltip:
911
- 'Coiled coils are built by two or more alpha-helices that wind around each other to form a supercoil',
912
- },
913
- ],
914
- },
915
854
  ],
916
855
  };
917
856
 
@@ -1,9 +1,8 @@
1
1
  import { LitElement, html, svg, TemplateResult, css, nothing } from 'lit';
2
- import { customElement } from 'lit/decorators.js';
2
+ import { customElement, state } from 'lit/decorators.js';
3
3
  import { unsafeHTML } from 'lit/directives/unsafe-html.js';
4
4
  import NightingaleStructure, {
5
5
  PredictionData,
6
- StructureData,
7
6
  } from '@nightingale-elements/nightingale-structure';
8
7
  import ProtvistaDatatable from 'protvista-datatable';
9
8
  import { fetchAll, loadComponent } from './utils';
@@ -21,26 +20,111 @@ const PDBLinks = [
21
20
  const alphaFoldLink = 'https://alphafold.ebi.ac.uk/entry/';
22
21
  const foldseekLink = `https://search.foldseek.com/search`;
23
22
 
23
+ // Excluded sources from 3d-beacons are PDBe and AlphaFold models as we fetch them separately from their respective API's
24
+ const providersFrom3DBeacons = [
25
+ 'SWISS-MODEL',
26
+ 'ModelArchive',
27
+ 'PED',
28
+ 'SASBDB',
29
+ 'isoform.io',
30
+ 'AlphaFill',
31
+ 'HEGELAB',
32
+ 'levylab',
33
+ ];
34
+
35
+ type UniProtKBData = {
36
+ uniProtKBCrossReferences: UniProtKBCrossReference[];
37
+ sequence: Sequence;
38
+ };
39
+
40
+ type UniProtKBCrossReference = {
41
+ database: string;
42
+ id: string;
43
+ properties: Record<string, string>[];
44
+ };
45
+
46
+ type Sequence = {
47
+ value: string;
48
+ length: number;
49
+ molWeight: number;
50
+ crc64: string;
51
+ md5: string;
52
+ };
53
+
54
+ type BeaconsData = {
55
+ uniprot_entry: {
56
+ ac: string;
57
+ id: string;
58
+ uniprot_checksum: string;
59
+ sequence_length: number;
60
+ segment_start: number;
61
+ segment_end: number;
62
+ };
63
+ structures: {
64
+ summary: {
65
+ model_identifier: string;
66
+ model_category: string;
67
+ model_url: string;
68
+ model_format: string;
69
+ model_type: string | null;
70
+ model_page_url: string;
71
+ provider: string;
72
+ number_of_conformers: number | null;
73
+ ensemble_sample_url: string | null;
74
+ ensemble_sample_format: string | null;
75
+ created: string;
76
+ sequence_identity: number;
77
+ uniprot_start: number;
78
+ uniprot_end: number;
79
+ coverage: number;
80
+ experimental_method: string | null;
81
+ resolution: number | null;
82
+ confidence_type: string;
83
+ confidence_version: string | null;
84
+ confidence_avg_local_score: number;
85
+ oligomeric_state: string | null;
86
+ preferred_assembly_id: string | null;
87
+ entities: {
88
+ entity_type: string;
89
+ entity_poly_type: string;
90
+ identifier: string;
91
+ identifier_category: string;
92
+ description: string;
93
+ chain_ids: string[];
94
+ }[];
95
+ };
96
+ }[];
97
+ };
98
+
24
99
  type ProcessedStructureData = {
25
100
  id: string;
26
- source: 'PDB' | 'AlphaFold';
27
- method: string;
101
+ source: string;
102
+ method?: string;
28
103
  resolution?: string;
29
104
  chain?: string;
30
105
  positions?: string;
31
106
  downloadLink?: string;
107
+ sourceDBLink?: string;
32
108
  protvistaFeatureId: string;
33
109
  };
34
110
 
35
- const processPDBData = (data: StructureData): ProcessedStructureData[] =>
36
- data.dbReferences
37
- .filter((xref) => xref.type === 'PDB')
111
+ const processPDBData = (data: UniProtKBData): ProcessedStructureData[] =>
112
+ data.uniProtKBCrossReferences
113
+ .filter((xref) => xref.database === 'PDB')
38
114
  .sort((refA, refB) => refA.id.localeCompare(refB.id))
39
115
  .map(({ id, properties }) => {
40
116
  if (!properties) {
41
117
  return;
42
118
  }
43
- const { chains, resolution, method } = properties;
119
+
120
+ const propertyMap = properties.reduce((acc, item) => {
121
+ acc[item.key] = item.value;
122
+ return acc;
123
+ }, {} as Record<string, string>);
124
+
125
+ const method = propertyMap['Method'];
126
+ const resolution = propertyMap['Resolution'];
127
+ const chains = propertyMap['Chains'];
44
128
 
45
129
  let chain;
46
130
  let positions;
@@ -79,6 +163,20 @@ const processAFData = (data: PredictionData[]): ProcessedStructureData[] =>
79
163
  downloadLink: d.pdbUrl,
80
164
  }));
81
165
 
166
+ const process3DBeaconsData = (data: BeaconsData): ProcessedStructureData[] => {
167
+ const otherStructures = data.structures.filter(({ summary }) =>
168
+ providersFrom3DBeacons.includes(summary.provider)
169
+ );
170
+ return otherStructures.map(({ summary }) => ({
171
+ id: summary['model_identifier'],
172
+ source: summary.provider,
173
+ positions: `${summary['uniprot_start']}-${summary['uniprot_end']}`,
174
+ protvistaFeatureId: summary['model_identifier'],
175
+ downloadLink: summary['model_url'],
176
+ sourceDBLink: summary['model_page_url'],
177
+ }));
178
+ };
179
+
82
180
  const AFMetaInfo = html`
83
181
  <strong>Model Confidence:</strong>
84
182
  <ul class="no-bullet">
@@ -151,6 +249,9 @@ class ProtvistaUniprotStructure extends LitElement {
151
249
  private loading?: boolean;
152
250
  private alphamissenseAvailable?: boolean;
153
251
 
252
+ @state()
253
+ private modelUrl = '';
254
+
154
255
  constructor() {
155
256
  super();
156
257
  loadComponent('nightingale-structure', NightingaleStructure);
@@ -176,22 +277,34 @@ class ProtvistaUniprotStructure extends LitElement {
176
277
  async connectedCallback() {
177
278
  super.connectedCallback();
178
279
  if (!this.accession) return;
179
- // https://www.ebi.ac.uk/pdbe/api/mappings/best_structures/${this.accession}
180
- const pdbUrl = `https://www.ebi.ac.uk/proteins/api/proteins/${this.accession}`;
181
- const alphaFoldURl = `https://alphafold.ebi.ac.uk/api/prediction/${this.accession}`;
182
280
 
183
- const rawData = await fetchAll([pdbUrl, alphaFoldURl]);
281
+ // We are showing PDBe models returned by UniProt's API as there is inconsistency between UniProt's recognised ones and 3d-beacons.
282
+ const pdbUrl = `https://rest.uniprot.org/uniprotkb/${this.accession}`;
283
+ const alphaFoldUrl = `https://alphafold.ebi.ac.uk/api/prediction/${this.accession}`;
284
+ // exclude_provider accepts only value hence 'pdbe' as majority of the models are from there.
285
+ const beaconsUrl = `https://www.ebi.ac.uk/pdbe/pdbe-kb/3dbeacons/api/uniprot/summary/${this.accession}.json?exclude_provider=pdbe`;
184
286
 
287
+ const rawData = await fetchAll([pdbUrl, alphaFoldUrl, beaconsUrl]);
185
288
  this.loading = false;
289
+
290
+ const pdbData = processPDBData(rawData[pdbUrl] || []);
291
+ let afData = [];
292
+ // Check if AF sequence matches UniProt sequence
293
+ if (
294
+ rawData[pdbUrl].sequence?.value === rawData[alphaFoldUrl][0]?.sequence
295
+ ) {
296
+ afData = processAFData(rawData[alphaFoldUrl] || []);
297
+ }
298
+ const beaconsData = process3DBeaconsData(rawData[beaconsUrl] || []);
299
+
186
300
  // TODO: return if no data at all
187
301
  // if (!payload) return;
188
- const pdbData = processPDBData(rawData[pdbUrl] || []);
189
- const afData = processAFData(rawData[alphaFoldURl] || []);
190
- const data = [...pdbData, ...afData];
302
+
303
+ const data = [...pdbData, ...afData, ...beaconsData];
191
304
  if (!data || !data.length) return;
192
305
 
193
306
  this.data = data;
194
- this.alphamissenseAvailable = rawData[alphaFoldURl].some(
307
+ this.alphamissenseAvailable = rawData[alphaFoldUrl].some(
195
308
  (data) => data.amAnnotationsUrl
196
309
  );
197
310
  }
@@ -233,12 +346,25 @@ class ProtvistaUniprotStructure extends LitElement {
233
346
  }
234
347
  }
235
348
 
236
- onTableRowClick({ id }: { id: string }) {
237
- this.structureId = id;
238
- if (this.structureId.startsWith('AF-')) {
239
- this.metaInfo = AFMetaInfo;
349
+ onTableRowClick({
350
+ id,
351
+ source,
352
+ downloadLink,
353
+ }: {
354
+ id: string;
355
+ source?: string;
356
+ downloadLink?: string;
357
+ }) {
358
+ if (providersFrom3DBeacons.includes(source)) {
359
+ this.modelUrl = downloadLink;
360
+ this.structureId = undefined;
240
361
  } else {
241
- this.metaInfo = undefined;
362
+ this.structureId = id;
363
+ if (this.structureId.startsWith('AF-')) {
364
+ this.metaInfo = AFMetaInfo;
365
+ } else {
366
+ this.metaInfo = undefined;
367
+ }
242
368
  }
243
369
  }
244
370
 
@@ -358,7 +484,10 @@ class ProtvistaUniprotStructure extends LitElement {
358
484
  protein-accession=${this.accession}
359
485
  color-theme=${this.colorTheme}
360
486
  ></nightingale-structure>`
361
- : html``}
487
+ : html`<nightingale-structure
488
+ model-url=${this.modelUrl}
489
+ color-theme=${this.colorTheme}
490
+ ></nightingale-structure>`}
362
491
  </div>
363
492
  <div class="protvista-uniprot-structure__table">
364
493
  ${this.data && this.data.length
@@ -386,9 +515,11 @@ class ProtvistaUniprotStructure extends LitElement {
386
515
  chain,
387
516
  positions,
388
517
  downloadLink,
518
+ sourceDBLink,
389
519
  }) => html`<tr
390
520
  data-id="${id}"
391
- @click="${() => this.onTableRowClick({ id })}"
521
+ @click="${() =>
522
+ this.onTableRowClick({ id, source, downloadLink })}"
392
523
  >
393
524
  <td data-filter="source" data-filter-value="${source}">
394
525
  <strong>${source}</strong>
@@ -415,23 +546,31 @@ class ProtvistaUniprotStructure extends LitElement {
415
546
  (prev, curr) => html` ${prev} · ${curr} `
416
547
  )}
417
548
  `
418
- : html`<a href="${alphaFoldLink}${this.accession}"
549
+ : ``}
550
+ ${source === 'AlphaFold'
551
+ ? html`<a href="${alphaFoldLink}${this.accession}"
419
552
  >AlphaFold</a
420
- >`}
553
+ >`
554
+ : ``}
555
+ ${sourceDBLink
556
+ ? html`<a href="${sourceDBLink}">${source}</a>`
557
+ : ``}
421
558
  </td>
422
559
  <td>
423
560
  ${downloadLink
424
561
  ? html`<a
425
- href="${downloadLink}"
426
- class="download-link"
427
- >${svg`${unsafeHTML(downloadIcon)}`}</a
428
- >
429
- ·
430
- ${foldseekURL(
431
- source === 'PDB' ? id : this.accession,
432
- source === 'PDB' ? 'PDB' : 'AlphaFoldDB'
433
- )}`
562
+ href="${downloadLink}"
563
+ class="download-link"
564
+ >${svg`${unsafeHTML(downloadIcon)}`}</a
565
+ > `
434
566
  : ''}
567
+ ${source === 'PDB' || source === 'AlphaFold'
568
+ ? html`·
569
+ ${foldseekURL(
570
+ source === 'PDB' ? id : this.accession,
571
+ source === 'PDB' ? 'PDB' : 'AlphaFoldDB'
572
+ )}`
573
+ : ``}
435
574
  </td>
436
575
  </tr>`
437
576
  )}