|
@@ -6,17 +6,16 @@
|
|
|
|
|
|
import { BehaviorSubject } from 'rxjs';
|
|
|
import { DefaultPluginSpec } from 'molstar/lib/mol-plugin';
|
|
|
-import { Plugin } from 'molstar/lib/mol-plugin-ui/plugin'
|
|
|
-import './index.html'
|
|
|
-import './favicon.ico'
|
|
|
+import { Plugin } from 'molstar/lib/mol-plugin-ui/plugin';
|
|
|
import { PluginContext } from 'molstar/lib/mol-plugin/context';
|
|
|
import { PluginCommands } from 'molstar/lib/mol-plugin/commands';
|
|
|
import { ViewerState as ViewerState, CollapsedState, ModelUrlProvider } from './types';
|
|
|
import { PluginSpec } from 'molstar/lib/mol-plugin/spec';
|
|
|
|
|
|
import { ColorNames } from 'molstar/lib/mol-util/color/names';
|
|
|
-import ReactDOM = require('react-dom');
|
|
|
-import React = require('react');
|
|
|
+import * as React from 'react';
|
|
|
+import * as ReactDOM from 'react-dom';
|
|
|
+
|
|
|
import { ModelLoader } from './helpers/model';
|
|
|
import { PresetProps } from './helpers/preset';
|
|
|
import { ControlsWrapper } from './ui/controls';
|
|
@@ -30,15 +29,23 @@ import { ObjectKeys } from 'molstar/lib/mol-util/type-helpers';
|
|
|
import { PluginLayoutControlsDisplay } from 'molstar/lib/mol-plugin/layout';
|
|
|
import { SuperposeColorThemeProvider } from './helpers/superpose/color';
|
|
|
import { encodeStructureData, downloadAsZipFile } from './helpers/export';
|
|
|
-require('./skin/rcsb.scss')
|
|
|
+import {Structure} from 'molstar/lib/mol-model/structure/structure';
|
|
|
+import {Script} from 'molstar/lib/mol-script/script';
|
|
|
+import {MolScriptBuilder} from 'molstar/lib/mol-script/language/builder';
|
|
|
+import {SetUtils} from 'molstar/lib/mol-util/set';
|
|
|
+import {Loci} from 'molstar/lib/mol-model/loci';
|
|
|
+import {StructureSelection} from 'molstar/lib/mol-model/structure/query';
|
|
|
+import {StructureRef} from 'molstar/lib/mol-plugin-state/manager/structure/hierarchy-state';
|
|
|
+import {StructureSelectionQuery} from 'molstar/lib/mol-plugin-state/helpers/structure-selection-query';
|
|
|
+import {StructureRepresentationRegistry} from 'molstar/lib/mol-repr/structure/registry';
|
|
|
|
|
|
/** package version, filled in at bundle build time */
|
|
|
-declare const __RCSB_MOLSTAR_VERSION__: string
|
|
|
-export const RCSB_MOLSTAR_VERSION = __RCSB_MOLSTAR_VERSION__;
|
|
|
+declare const __RCSB_MOLSTAR_VERSION__: string;
|
|
|
+export const RCSB_MOLSTAR_VERSION = typeof __RCSB_MOLSTAR_VERSION__ != 'undefined' ? __RCSB_MOLSTAR_VERSION__ : 'none';
|
|
|
|
|
|
/** unix time stamp, to be filled in at bundle build time */
|
|
|
-declare const __BUILD_TIMESTAMP__: number
|
|
|
-export const BUILD_TIMESTAMP = __BUILD_TIMESTAMP__;
|
|
|
+declare const __BUILD_TIMESTAMP__: number;
|
|
|
+export const BUILD_TIMESTAMP = typeof __BUILD_TIMESTAMP__ != 'undefined' ? __BUILD_TIMESTAMP__ : 'none';
|
|
|
export const BUILD_DATE = new Date(BUILD_TIMESTAMP);
|
|
|
|
|
|
const Extensions = {
|
|
@@ -86,20 +93,20 @@ const DefaultViewerProps = {
|
|
|
custom: false
|
|
|
}
|
|
|
};
|
|
|
-type ViewerProps = typeof DefaultViewerProps
|
|
|
+export type ViewerProps = typeof DefaultViewerProps
|
|
|
|
|
|
export class Viewer {
|
|
|
private readonly plugin: PluginContext;
|
|
|
private readonly modelUrlProviders: ModelUrlProvider[];
|
|
|
|
|
|
private get customState() {
|
|
|
- return this.plugin.customState as ViewerState
|
|
|
+ return this.plugin.customState as ViewerState;
|
|
|
}
|
|
|
|
|
|
constructor(target: string | HTMLElement, props: Partial<ViewerProps> = {}) {
|
|
|
- target = typeof target === 'string' ? document.getElementById(target)! : target
|
|
|
+ target = typeof target === 'string' ? document.getElementById(target)! : target;
|
|
|
|
|
|
- const o = { ...DefaultViewerProps, ...props }
|
|
|
+ const o = { ...DefaultViewerProps, ...props };
|
|
|
|
|
|
const spec: PluginSpec = {
|
|
|
actions: [...DefaultPluginSpec.actions],
|
|
@@ -119,7 +126,7 @@ export class Viewer {
|
|
|
...DefaultPluginSpec.layout && DefaultPluginSpec.layout.controls,
|
|
|
top: o.layoutShowSequence ? undefined : 'none',
|
|
|
bottom: o.layoutShowLog ? undefined : 'none',
|
|
|
- // left: 'none',
|
|
|
+ left: 'none',
|
|
|
right: ControlsWrapper,
|
|
|
}
|
|
|
},
|
|
@@ -147,6 +154,7 @@ export class Viewer {
|
|
|
modelLoader: new ModelLoader(this.plugin),
|
|
|
collapsed: new BehaviorSubject<CollapsedState>({
|
|
|
selection: true,
|
|
|
+ strucmotifSubmit: true,
|
|
|
measurements: true,
|
|
|
superposition: true,
|
|
|
component: false,
|
|
@@ -165,13 +173,33 @@ export class Viewer {
|
|
|
this.plugin.representation.structure.themes.colorThemeRegistry.add(SuperposeColorThemeProvider);
|
|
|
// this.plugin.builders.structure.representation.registerPreset(RcsbSuperpositionRepresentationPreset);
|
|
|
|
|
|
+ ReactDOM.render(React.createElement(Plugin, { plugin: this.plugin }), target);
|
|
|
+ // TODO Check why this.plugin.canvas3d can be null
|
|
|
+ // this.plugin.canvas3d can be null. The value is not assigned until React Plugin component is mounted
|
|
|
+ // Next wait Promise guarantees that its value is defined
|
|
|
+ const wait: Promise<void> = new Promise<void>((resolve, reject)=>{
|
|
|
+ const recursive: () => void = () => {
|
|
|
+ if(this.plugin.canvas3d != null){
|
|
|
+ resolve();
|
|
|
+ }else{
|
|
|
+ setTimeout(()=>{
|
|
|
+ recursive();
|
|
|
+ }, 100);
|
|
|
+ }
|
|
|
+ };
|
|
|
+ recursive();
|
|
|
+ });
|
|
|
+ wait.then(result=>{
|
|
|
+ const renderer = this.plugin.canvas3d!.props.renderer;
|
|
|
+ PluginCommands.Canvas3D.SetSettings(this.plugin, { settings: { renderer: { ...renderer, backgroundColor: o.backgroundColor } } });
|
|
|
+ });
|
|
|
if (o.showWelcomeToast) {
|
|
|
PluginCommands.Toast.Show(this.plugin, {
|
|
|
title: 'Welcome',
|
|
|
message: `RCSB PDB Mol* Viewer ${RCSB_MOLSTAR_VERSION} [${BUILD_DATE.toLocaleString()}]`,
|
|
|
key: 'toast-welcome',
|
|
|
timeoutMs: 5000
|
|
|
- })
|
|
|
+ });
|
|
|
}
|
|
|
this.prevExpanded = this.plugin.layout.state.isExpanded;
|
|
|
this.plugin.layout.events.updated.subscribe(() => this.toggleControls());
|
|
@@ -201,17 +229,17 @@ export class Viewer {
|
|
|
|
|
|
clear() {
|
|
|
const state = this.plugin.state.data;
|
|
|
- return PluginCommands.State.RemoveObject(this.plugin, { state, ref: state.tree.root.ref })
|
|
|
+ return PluginCommands.State.RemoveObject(this.plugin, { state, ref: state.tree.root.ref });
|
|
|
}
|
|
|
|
|
|
async loadPdbId(pdbId: string, props?: PresetProps, matrix?: Mat4) {
|
|
|
for (const provider of this.modelUrlProviders) {
|
|
|
try {
|
|
|
- const p = provider(pdbId)
|
|
|
- await this.customState.modelLoader.load({ fileOrUrl: p.url, format: p.format, isBinary: p.isBinary }, props, matrix)
|
|
|
- break
|
|
|
+ const p = provider(pdbId);
|
|
|
+ await this.customState.modelLoader.load({ fileOrUrl: p.url, format: p.format, isBinary: p.isBinary }, props, matrix);
|
|
|
+ break;
|
|
|
} catch (e) {
|
|
|
- console.warn(`loading '${pdbId}' failed with '${e}', trying next model-loader-provider`)
|
|
|
+ console.warn(`loading '${pdbId}' failed with '${e}', trying next model-loader-provider`);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
@@ -224,7 +252,7 @@ export class Viewer {
|
|
|
}
|
|
|
|
|
|
loadStructureFromUrl(url: string, format: BuiltInTrajectoryFormat, isBinary: boolean, props?: PresetProps, matrix?: Mat4) {
|
|
|
- return this.customState.modelLoader.load({ fileOrUrl: url, format, isBinary }, props, matrix)
|
|
|
+ return this.customState.modelLoader.load({ fileOrUrl: url, format, isBinary }, props, matrix);
|
|
|
}
|
|
|
|
|
|
loadSnapshotFromUrl(url: string, type: PluginState.SnapshotType) {
|
|
@@ -243,4 +271,158 @@ export class Viewer {
|
|
|
const content = encodeStructureData(this.plugin);
|
|
|
downloadAsZipFile(content);
|
|
|
}
|
|
|
+
|
|
|
+ pluginCall(f: (plugin: PluginContext) => void){
|
|
|
+ f(this.plugin);
|
|
|
+ }
|
|
|
+
|
|
|
+ public getPlugin(): PluginContext {
|
|
|
+ return this.plugin;
|
|
|
+ }
|
|
|
+
|
|
|
+ public select(selection: Array<{modelId: string; asymId: string; position: number;}>, mode: 'select'|'hover', modifier: 'add'|'set'): void;
|
|
|
+ public select(modelId: string, asymId: string, position: number, mode: 'select'|'hover', modifier: 'add'|'set'): void;
|
|
|
+ public select(modelId: string, asymId: string, begin: number, end: number, mode: 'select'|'hover', modifier: 'add'|'set'): void;
|
|
|
+ public select(...args: any[]){
|
|
|
+ if(args.length === 3){
|
|
|
+ if(args[2] === 'set')
|
|
|
+ this.clearSelection('select');
|
|
|
+ (args[0] as Array<{modelId: string; asymId: string; position: number;}>).forEach(r=>{
|
|
|
+ this.selectSegment(r.modelId, r.asymId, r.position, r.position, args[1], 'add');
|
|
|
+ });
|
|
|
+ }else if(args.length === 5){
|
|
|
+ this.selectSegment(args[0], args[1], args[2], args[2], args[3], args[4]);
|
|
|
+ }else if(args.length === 6){
|
|
|
+ this.selectSegment(args[0], args[1], args[2], args[3], args[4], args[5]);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ private selectSegment(modelId: string, asymId: string, begin: number, end: number, mode: 'select'|'hover', modifier: 'add'|'set'): void {
|
|
|
+ const data: Structure | undefined = getStructureWithModelId(this.plugin.managers.structure.hierarchy.current.structures, modelId);
|
|
|
+ if (data == null) return;
|
|
|
+ const seq_id: Array<number> = new Array<number>();
|
|
|
+ for(let n = begin; n <= end; n++){
|
|
|
+ seq_id.push(n);
|
|
|
+ }
|
|
|
+ const sel: StructureSelection = Script.getStructureSelection(Q => Q.struct.generator.atomGroups({
|
|
|
+ 'chain-test': Q.core.rel.eq([asymId, MolScriptBuilder.ammp('label_asym_id')]),
|
|
|
+ 'residue-test': Q.core.set.has([MolScriptBuilder.set(...SetUtils.toArray(new Set(seq_id))), MolScriptBuilder.ammp('label_seq_id')])
|
|
|
+ }), data);
|
|
|
+ const loci: Loci = StructureSelection.toLociWithSourceUnits(sel);
|
|
|
+ if(mode == null || mode === 'select')
|
|
|
+ this.plugin.managers.structure.selection.fromLoci(modifier, loci);
|
|
|
+ else if(mode === 'hover')
|
|
|
+ this.plugin.managers.interactivity.lociHighlights.highlight({ loci });
|
|
|
+ }
|
|
|
+ public clearSelection(mode: 'select'|'hover', options?: {modelId: string; labelAsymId: string;}): void {
|
|
|
+ if(mode == null || mode === 'select') {
|
|
|
+ if(options == null){
|
|
|
+ this.plugin.managers.interactivity.lociSelects.deselectAll();
|
|
|
+ }else{
|
|
|
+ const data: Structure | undefined = getStructureWithModelId(this.plugin.managers.structure.hierarchy.current.structures, options.modelId);
|
|
|
+ if (data == null) return;
|
|
|
+ const sel: StructureSelection = Script.getStructureSelection(Q => Q.struct.generator.atomGroups({
|
|
|
+ 'chain-test': Q.core.rel.eq([options.labelAsymId, MolScriptBuilder.ammp('label_asym_id')])
|
|
|
+ }), data);
|
|
|
+ const loci: Loci = StructureSelection.toLociWithSourceUnits(sel);
|
|
|
+ this.plugin.managers.interactivity.lociSelects.deselect({loci});
|
|
|
+ }
|
|
|
+ }else if(mode === 'hover') {
|
|
|
+ this.plugin.managers.interactivity.lociHighlights.clearHighlights();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public createComponent(componentId: string, modelId: string, asymId: string, representationType: StructureRepresentationRegistry.BuiltIn): Promise<void>;
|
|
|
+ public createComponent(componentId: string, modelId: string, residues: Array<{asymId: string, position: number}>, representationType: StructureRepresentationRegistry.BuiltIn): Promise<void>;
|
|
|
+ public createComponent(componentId: string, modelId: string, asymId: string, begin: number, end: number, representationType: StructureRepresentationRegistry.BuiltIn): Promise<void>;
|
|
|
+ public createComponent(...args: any[]): Promise<void>{
|
|
|
+ if(args.length === 4 && typeof args[2] === 'string'){
|
|
|
+ return this.createComponentFromChain(args[0], args[1], args[2], args[3]);
|
|
|
+ }else if(args.length === 4 && args[2] instanceof Array){
|
|
|
+ return this.createComponentFromSet(args[0], args[1], args[2], args[3]);
|
|
|
+ }else if(args.length === 6 ){
|
|
|
+ return this.createComponentFromRange(args[0], args[1], args[2], args[3], args[4], args[5]);
|
|
|
+ }
|
|
|
+ throw 'createComponent error: wrong arguments';
|
|
|
+ }
|
|
|
+ private async createComponentFromChain(componentId: string, modelId: string, asymId: string, representationType: StructureRepresentationRegistry.BuiltIn): Promise<void>{
|
|
|
+ const structureRef: StructureRef | undefined = getStructureRefWithModelId(this.plugin.managers.structure.hierarchy.current.structures, modelId);
|
|
|
+ if(structureRef == null)
|
|
|
+ return;
|
|
|
+ await this.plugin.managers.structure.component.add({
|
|
|
+ selection: StructureSelectionQuery(
|
|
|
+ 'innerQuery_' + Math.random().toString(36).substr(2),
|
|
|
+ MolScriptBuilder.struct.generator.atomGroups({
|
|
|
+ 'chain-test': MolScriptBuilder.core.rel.eq([asymId, MolScriptBuilder.ammp('label_asym_id')])
|
|
|
+ })
|
|
|
+ ),
|
|
|
+ options: { checkExisting: false, label: componentId },
|
|
|
+ representation: representationType,
|
|
|
+ }, [structureRef]);
|
|
|
+ }
|
|
|
+ private async createComponentFromSet(componentId: string, modelId: string, residues: Array<{asymId: string, position: number}>, representationType: StructureRepresentationRegistry.BuiltIn): Promise<void>{
|
|
|
+ const structureRef: StructureRef | undefined = getStructureRefWithModelId(this.plugin.managers.structure.hierarchy.current.structures, modelId);
|
|
|
+ if(structureRef == null)
|
|
|
+ return;
|
|
|
+ await this.plugin.managers.structure.component.add({
|
|
|
+ selection: StructureSelectionQuery(
|
|
|
+ 'innerQuery_' + Math.random().toString(36).substr(2),
|
|
|
+ MolScriptBuilder.struct.combinator.merge(
|
|
|
+ residues.map(r=>MolScriptBuilder.struct.generator.atomGroups({
|
|
|
+ 'chain-test': MolScriptBuilder.core.rel.eq([r.asymId, MolScriptBuilder.ammp('label_asym_id')]),
|
|
|
+ 'residue-test': MolScriptBuilder.core.rel.eq([r.position, MolScriptBuilder.ammp('label_seq_id')])
|
|
|
+ }))
|
|
|
+ )
|
|
|
+ ),
|
|
|
+ options: { checkExisting: false, label: componentId },
|
|
|
+ representation: representationType,
|
|
|
+ }, [structureRef]);
|
|
|
+ }
|
|
|
+ private async createComponentFromRange(componentId: string, modelId: string, asymId: string, begin: number, end: number, representationType: StructureRepresentationRegistry.BuiltIn): Promise<void>{
|
|
|
+ const structureRef: StructureRef | undefined = getStructureRefWithModelId(this.plugin.managers.structure.hierarchy.current.structures, modelId);
|
|
|
+ if(structureRef == null)
|
|
|
+ return;
|
|
|
+ const seq_id: Array<number> = new Array<number>();
|
|
|
+ for(let n = begin; n <= end; n++){
|
|
|
+ seq_id.push(n);
|
|
|
+ }
|
|
|
+ await this.plugin.managers.structure.component.add({
|
|
|
+ selection: StructureSelectionQuery(
|
|
|
+ 'innerQuery_' + Math.random().toString(36).substr(2),
|
|
|
+ MolScriptBuilder.struct.generator.atomGroups({
|
|
|
+ 'chain-test': MolScriptBuilder.core.rel.eq([asymId, MolScriptBuilder.ammp('label_asym_id')]),
|
|
|
+ 'residue-test': MolScriptBuilder.core.set.has([MolScriptBuilder.set(...SetUtils.toArray(new Set(seq_id))), MolScriptBuilder.ammp('label_seq_id')])
|
|
|
+ })
|
|
|
+ ),
|
|
|
+ options: { checkExisting: false, label: componentId },
|
|
|
+ representation: representationType,
|
|
|
+ }, [structureRef]);
|
|
|
+ }
|
|
|
+
|
|
|
+ public removeComponent(componentId: string): void{
|
|
|
+ this.plugin.managers.structure.hierarchy.currentComponentGroups.forEach(c=>{
|
|
|
+ for(const comp of c){
|
|
|
+ if(comp.cell.obj?.label === componentId) {
|
|
|
+ this.plugin.managers.structure.hierarchy.remove(c);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function getStructureRefWithModelId(structures: StructureRef[], modelId: string): StructureRef|undefined{
|
|
|
+ for(const structure of structures){
|
|
|
+ if(!structure.cell?.obj?.data?.units)
|
|
|
+ continue;
|
|
|
+ const unit = structure.cell.obj.data.units[0];
|
|
|
+ const id: string = unit.model.id;
|
|
|
+ if(id === modelId)
|
|
|
+ return structure;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function getStructureWithModelId(structures: StructureRef[], modelId: string): Structure|undefined{
|
|
|
+ const structureRef: StructureRef | undefined = getStructureRefWithModelId(structures, modelId);
|
|
|
+ if(structureRef != null)
|
|
|
+ return structureRef.cell?.obj?.data;
|
|
|
}
|