Переглянути джерело

Merge branch 'dev-sb-v2' of github.com:rcsb/rcsb-molstar into dev-sb-v2

# Conflicts:
#	src/viewer/helpers/viewer.ts
bioinsilico 3 роки тому
батько
коміт
7df6ac5d87

+ 1 - 4
.vscode/launch.json

@@ -1,14 +1,11 @@
 {
-    // Use IntelliSense to learn about possible attributes.
-    // Hover to view descriptions of existing attributes.
-    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
     "version": "0.2.0",
     "configurations": [
         {
             "type": "chrome",
             "request": "launch",
             "name": "Launch RCSB PDB Mol* Viewer",
-            "url": "http://localhost:1335/build/dist/structure-viewer/index.html?pdbId=4zjx&assemblyId=1",
+            "url": "http://localhost:1335/build/dist/structure/index.html?pdbId=4zjx&assemblyId=1",
             "sourceMaps": true,
             "webRoot": "${workspaceFolder}"
         }

+ 1 - 1
.vscode/settings.json

@@ -5,5 +5,5 @@
         "*.frag.ts": "glsl",
         "*.vert.ts": "glsl",
         "*.gql.ts": "graphql"
-    },
+    }
 }

+ 2 - 1
README.md

@@ -6,10 +6,11 @@ RCSB PDB implementation of [Mol* (/'mol-star/)](https://github.com/molstar/molst
 Try it [here](https://rcsb.org/3d-view/).
 
 PDBe also maintains a flavor of Mol* called [PDBe Molstar](https://github.com/PDBeurope/pdbe-molstar).
+Documentation of the parent Mol* project can be found [here](https://molstar.org/docs/). See `index.html` for examples.
 
 ## Functionality
 Provides custom features used in the Mol* viewer on [rcsb.org](https://www.rcsb.org/3d-view):
-- visualization of structure alignment
+- visualization of structure alignments
 - visualization of structure motifs & UI to launch structure motif queries
 - interactivity functionality to highlight and add representations for selections of a structure, used in the [3D Protein Feature View](https://www.rcsb.org/3d-sequence/4hhb)
 - linkable focus representation on ligands or chains

+ 1 - 1
package-lock.json

@@ -1,6 +1,6 @@
 {
     "name": "@rcsb/rcsb-molstar",
-    "version": "2.0.0-dev.8",
+    "version": "2.0.0-dev.9",
     "lockfileVersion": 1,
     "requires": true,
     "dependencies": {

+ 1 - 1
package.json

@@ -1,6 +1,6 @@
 {
     "name": "@rcsb/rcsb-molstar",
-    "version": "2.0.0-dev.8",
+    "version": "2.0.0-dev.10",
     "description": "RCSB PDB apps and props based on Mol*.",
     "homepage": "https://github.com/rcsb/rcsb-molstar#readme",
     "repository": {

+ 2 - 2
src/viewer/helpers/af-confidence/behavior.ts

@@ -20,7 +20,7 @@ export const AlphaFoldConfidenceScore = PluginBehavior.create<{ autoAttach: bool
         description: 'AlphaFold Confidence Score.'
     },
     ctor: class extends PluginBehavior.Handler<{ autoAttach: boolean, showTooltip: boolean }> {
-        private provider = AlphaFoldConfidenceProvider
+        private provider = AlphaFoldConfidenceProvider;
 
         private labelProvider = {
             label: (loci: Loci): string | undefined => {
@@ -40,7 +40,7 @@ export const AlphaFoldConfidenceScore = PluginBehavior.create<{ autoAttach: bool
                     default: return;
                 }
             }
-        }
+        };
 
         register(): void {
             this.ctx.customModelProperties.register(this.provider, this.params.autoAttach);

+ 5 - 1
src/viewer/helpers/af-confidence/prop.ts

@@ -129,9 +129,13 @@ function createScoreMapFromCif(modelData: Model, residueData: Table<typeof Alpha
         return 'Very low';
     };
 
+    const entityMap = new Map<string, string>();
     for (let i = 0; i < _rowCount; i++) {
         const confidenceScore = metric_value.value(i);
-        const idx = modelData.atomicHierarchy.index.findResidue('1', label_asym_id.value(i), label_seq_id.value(i));
+        const labelAsymId = label_asym_id.value(i);
+        if (!entityMap.has(labelAsymId)) entityMap.set(labelAsymId, (modelData.atomicHierarchy.index.findEntity(labelAsymId) + 1).toString());
+        const entityId = entityMap.get(labelAsymId)!;
+        const idx = modelData.atomicHierarchy.index.findResidue(entityId, labelAsymId, label_seq_id.value(i));
         const confidenceCategory = toCategory(confidenceScore);
 
         ret.set(idx, [confidenceScore, confidenceCategory]);

+ 1 - 1
src/viewer/helpers/preset.ts

@@ -348,7 +348,7 @@ function determineAssemblyId(traj: any, p: MotifProps) {
             g.split(',').forEach(e => {
                 const dashIndex = e.indexOf('-');
                 if (dashIndex > 0) {
-                    const from = parseInt(e.substring(0, dashIndex)), to = parseInt(e.substr(dashIndex + 1));
+                    const from = parseInt(e.substring(0, dashIndex)), to = parseInt(e.substring(dashIndex + 1));
                     for (let i = from; i <= to; i++) group[group.length] = i.toString();
                 } else {
                     group[group.length] = e.trim();

+ 1 - 1
src/viewer/helpers/superpose/flexible-structure.ts

@@ -17,7 +17,7 @@ const FlexibleStructureFromModel = PluginStateTransform.BuiltIn({
     from: SO.Molecule.Model,
     to: SO.Molecule.Structure,
     isDecorator: true,
-    params(a) {
+    params(_a) {
         return {
             targets: PD.Value<PropsetProps['targets']>([])
         };

+ 8 - 4
src/viewer/helpers/viewer.ts

@@ -11,7 +11,8 @@ import { MolScriptBuilder as MS } from 'molstar/lib/mol-script/language/builder'
 import { StructureRepresentationRegistry } from 'molstar/lib/mol-repr/structure/registry';
 import { StructureSelectionQuery } from 'molstar/lib/mol-plugin-state/helpers/structure-selection-query';
 import {
-    rangeToTest, SelectBase,
+    rangeToTest,
+    SelectBase,
     SelectRange,
     SelectTarget,
     Target,
@@ -85,7 +86,7 @@ export async function createComponent(plugin: PluginContext, componentLabel: str
         if (!structureRef) throw Error('createComponent error: model not found');
 
         const residues = toResidues(target);
-        const sel = StructureSelectionQuery('innerQuery_' + Math.random().toString(36).substr(2),
+        const sel = StructureSelectionQuery('innerQuery_' + Math.random().toString(36).substring(2),
             MS.struct.generator.atomGroups(rangeToTest(target.labelAsymId, residues, target.operatorName)));
         await plugin.managers.structure.component.add({
             selection: sel,
@@ -107,13 +108,16 @@ function toResidues(target: SelectBase | SelectTarget): number[] {
     return [];
 }
 
-export function removeComponent(plugin: PluginContext, componentLabel: string) {
+export async function removeComponent(plugin: PluginContext, componentLabel: string) {
+    const out: Promise<void>[] = [];
     plugin.managers.structure.hierarchy.currentComponentGroups.forEach(c => {
         for (const comp of c) {
             if (comp.cell.obj?.label === componentLabel) {
-                plugin.managers.structure.hierarchy.remove(c);
+                const o = plugin.managers.structure.hierarchy.remove(c);
+                if (o) out.push(o);
                 break;
             }
         }
     });
+    await Promise.all(out);
 }

+ 2 - 2
src/viewer/index.ts

@@ -297,8 +297,8 @@ export class Viewer {
         await createComponent(this._plugin, label, targets, representationType);
     }
 
-    removeComponent(componentLabel: string): void {
-        removeComponent(this._plugin, componentLabel);
+    async removeComponent(componentLabel: string) {
+        await removeComponent(this._plugin, componentLabel);
     }
 }
 

+ 1 - 1
src/viewer/ui/exchanges.tsx

@@ -42,7 +42,7 @@ export class ExchangesControl extends React.Component<{ handler: Residue }> {
     onClickSwatch = (e: React.MouseEvent<HTMLButtonElement>) => {
         const tlc = e.currentTarget.getAttribute('data-id')!;
         this.props.handler.toggleExchange(tlc);
-    }
+    };
 
     swatch() {
         // TODO update of isSelected style is delayed - this seems to be a Chrome-related bug

+ 1 - 1
src/viewer/ui/export.tsx

@@ -30,7 +30,7 @@ class CoordinatesExportControls extends PluginUIComponent {
     download = async () => {
         const content = encodeStructureData(this.plugin);
         await downloadAsZipFile(this.plugin, content);
-    }
+    };
 
     render() {
         return <>

+ 2 - 2
src/viewer/ui/strucmotif.tsx

@@ -222,7 +222,7 @@ class SubmitControls extends PurePluginUIComponent<{}, { isBusy: boolean, residu
         const url = sierraUrl + encodeURIComponent(JSON.stringify(query)) + RETURN_TYPE;
         // console.log(url);
         window.open(url, '_blank');
-    }
+    };
 
     sortResidueIds(a: ResidueSelection, b: ResidueSelection): number {
         if (a.label_asym_id !== b.label_asym_id) {
@@ -249,7 +249,7 @@ class SubmitControls extends PurePluginUIComponent<{}, { isBusy: boolean, residu
     selectAction: ActionMenu.OnSelect = item => {
         if (!item) return;
         (item?.value as any)();
-    }
+    };
 
     toggleExchanges = (idx: number) => this.setState({ action: (this.state.action === idx ? void 0 : idx) as ExchangeState });
 

+ 30 - 6
src/viewer/ui/validation.tsx

@@ -11,6 +11,8 @@ import { ValidationReportGeometryQualityPreset } from 'molstar/lib/extensions/rc
 import { ActionMenu } from 'molstar/lib/mol-plugin-ui/controls/action-menu';
 import { Model } from 'molstar/lib/mol-model/structure/model';
 import { MmcifFormat } from 'molstar/lib/mol-model-formats/structure/mmcif';
+import { AlphaFoldConfidence } from '../helpers/af-confidence/prop';
+import { AlphaFoldConfidenceColorThemeProvider } from '../helpers/af-confidence/color';
 
 interface ValidationReportState extends CollapsableState {
     errorStates: Set<string>
@@ -61,8 +63,10 @@ export class ValidationReportControls extends CollapsableControls<{}, Validation
         const { selection } = this.plugin.managers.structure.hierarchy;
         if (selection.structures.length !== 1) return false;
         const pivot = this.pivot.cell;
-        if (!pivot.obj) return false;
-        return pivot.obj.data.models.length === 1 && ValidationReport.isApplicable(pivot.obj.data.models[0]);
+        if (!pivot.obj || pivot.obj.data.models.length !== 1) return false;
+        const model = pivot.obj.data.models[0];
+        // all supported options must be registered here
+        return ValidationReport.isApplicable(model) || AlphaFoldConfidence.isApplicable(model);
     }
 
     get noValidationReport() {
@@ -72,6 +76,13 @@ export class ValidationReportControls extends CollapsableControls<{}, Validation
         return !model || !this.isFromPdbArchive(model);
     }
 
+    get alphaFoldData() {
+        const structure = this.pivot.cell.obj?.data;
+        if (!structure || structure.models.length !== 1) return false;
+        const model = structure.models[0];
+        return AlphaFoldConfidence.isApplicable(model);
+    }
+
     isFromPdbArchive(model: Model) {
         if (!MmcifFormat.is(model.sourceData)) return false;
         return model.entryId.match(/^[1-9][a-z0-9]{3}$/i) !== null ||
@@ -89,13 +100,16 @@ export class ValidationReportControls extends CollapsableControls<{}, Validation
                 return { errorStates: errors };
             });
         }
-    }
+    };
+
+    requestAlphaFoldConfidenceColoring = async () => {
+        await this.plugin.managers.structure.component.updateRepresentationsTheme(this.pivot.components, { color: AlphaFoldConfidenceColorThemeProvider.name as any });
+    };
 
     get actions(): ActionMenu.Items {
-        // TODO this could support other kinds of reports/validation like the AlphaFold confidence scores
         const noValidationReport = this.noValidationReport;
         const validationReportError = this.state.errorStates.has(ValidationReportTag);
-        return [
+        const out: ActionMenu.Items = [
             {
                 kind: 'item',
                 label: validationReportError ? 'Failed to Obtain Validation Report' : (noValidationReport ? 'No Validation Report Available' : 'RCSB PDB Validation Report'),
@@ -103,12 +117,22 @@ export class ValidationReportControls extends CollapsableControls<{}, Validation
                 disabled: noValidationReport || validationReportError
             },
         ];
+
+        if (this.alphaFoldData) {
+            out.push({
+                kind: 'item',
+                label: 'AlphaFold Confidence Scores',
+                value: this.requestAlphaFoldConfidenceColoring
+            });
+        }
+
+        return out;
     }
 
     selectAction: ActionMenu.OnSelect = item => {
         if (!item) return;
         (item?.value as any)();
-    }
+    };
 
     renderControls() {
         return <ActionMenu items={this.actions} onSelect={this.selectAction} />;