Quellcode durchsuchen

StructureViewerBehaviourInterface

bioinsilico vor 2 Jahren
Ursprung
Commit
299f8dfb62
24 geänderte Dateien mit 333 neuen und 184 gelöschten Zeilen
  1. 5 0
      CHANGELOG.md
  2. 1 1
      package.json
  3. 5 0
      src/RcsbFv3D/RcsbFv3DAbstract.tsx
  4. 2 0
      src/RcsbFv3D/RcsbFv3DAssembly.tsx
  5. 3 0
      src/RcsbFv3D/RcsbFv3DComponent.tsx
  6. 3 2
      src/RcsbFv3D/RcsbFv3DCustom.tsx
  7. 2 0
      src/RcsbFv3D/RcsbFv3DUniprot.tsx
  8. 0 1
      src/RcsbFvSequence/RcsbFvSequence.tsx
  9. 1 2
      src/RcsbFvSequence/SequenceViews/AbstractView.tsx
  10. 59 141
      src/RcsbFvSequence/SequenceViews/RcsbView/CallbackManagerFactoryImplementation/AssemblyCallbackManager.ts
  11. 3 3
      src/RcsbFvSequence/SequenceViews/RcsbView/CallbackManagerFactoryImplementation/UniprotCallbackManager.ts
  12. 10 11
      src/RcsbFvSequence/SequenceViews/RcsbView/CallbackManagerFactoryInterface.ts
  13. 0 1
      src/RcsbFvSequence/SequenceViews/RcsbView/PfvManagerFactoryImplementation/AssemblyPfvManagerFactory.tsx
  14. 4 5
      src/RcsbFvSequence/SequenceViews/RcsbView/RcsbView.tsx
  15. 13 9
      src/RcsbFvState/RcsbFvSelectorManager.ts
  16. 1 1
      src/RcsbFvState/RcsbFvStateInterface.ts
  17. 14 1
      src/RcsbFvStructure/RcsbFvStructure.tsx
  18. 139 0
      src/RcsbFvStructure/StructureViewerBehaviour/AssemblyBehaviour.ts
  19. 19 0
      src/RcsbFvStructure/StructureViewerBehaviour/NullBehaviour.ts
  20. 18 0
      src/RcsbFvStructure/StructureViewerBehaviour/UniprotBehaviour.ts
  21. 19 0
      src/RcsbFvStructure/StructureViewerBehaviourInterface.ts
  22. 1 1
      src/RcsbFvStructure/StructureViewerInterface.ts
  23. 10 4
      src/RcsbFvStructure/StructureViewers/MolstarViewer/MolstarCallbackManager.ts
  24. 1 1
      src/examples/structural-alignment/index.ts

+ 5 - 0
CHANGELOG.md

@@ -2,6 +2,11 @@
 
 [Semantic Versioning](https://semver.org/)
 
+## [2.1.0] - 2022-09-02
+### Major refactoring
+- `StructureViewerBehaviourObserverInterface`
+- `StructureViewerBehaviourInterface`
+
 ## [2.0.1] - 2022-09-01
 ### Dependency update
 - rcsb-saguaro-app v4.4.1

+ 1 - 1
package.json

@@ -1,6 +1,6 @@
 {
   "name": "@rcsb/rcsb-saguaro-3d",
-  "version": "2.0.1",
+  "version": "2.0.2",
   "description": "RCSB Molstar/Saguaro Web App",
   "main": "build/dist/app.js",
   "files": [

+ 5 - 0
src/RcsbFv3D/RcsbFv3DAbstract.tsx

@@ -7,6 +7,7 @@ import {EventType, RcsbFvContextManager} from "../RcsbFvContextManager/RcsbFvCon
 import {PluginContext} from "molstar/lib/mol-plugin/context";
 import {CSSProperties} from "react";
 import {StructureViewerInterface} from "../RcsbFvStructure/StructureViewerInterface";
+import {StructureViewerBehaviourObserverInterface} from "../RcsbFvStructure/StructureViewerBehaviourInterface";
 
 export interface RcsbFv3DAbstractInterface<T,R,S,U> {
     elementId: string;
@@ -14,6 +15,7 @@ export interface RcsbFv3DAbstractInterface<T,R,S,U> {
     sequenceConfig: RcsbFvSequenceInterface<T,R,U>;
     structureConfig: RcsbFvStructureConfigInterface<R,S>;
     structureViewer: StructureViewerInterface<R,S>;
+    structureViewerBehaviourObserver: StructureViewerBehaviourObserverInterface<R>;
 }
 
 export abstract class RcsbFv3DAbstract<T,R,S,U> {
@@ -22,6 +24,7 @@ export abstract class RcsbFv3DAbstract<T,R,S,U> {
     private reactRoot: Root;
     private readonly structureConfig: RcsbFvStructureConfigInterface<R,S>;
     private readonly structureViewer: StructureViewerInterface<R,S>;
+    private readonly structureViewerBehaviourObserver: StructureViewerBehaviourObserverInterface<R>;
     private readonly sequenceConfig: RcsbFvSequenceInterface<T,R,U>;
     private readonly ctxManager: RcsbFvContextManager<T,R,S,U> = new RcsbFvContextManager<T,R,S,U>();
     private fullScreenFlag: boolean = false;
@@ -38,6 +41,7 @@ export abstract class RcsbFv3DAbstract<T,R,S,U> {
        this.sequenceConfig = config.sequenceConfig;
        this.structureConfig = config.structureConfig;
        this.structureViewer = config.structureViewer;
+       this.structureViewerBehaviourObserver = config.structureViewerBehaviourObserver;
     }
 
     public render(): void{
@@ -60,6 +64,7 @@ export abstract class RcsbFv3DAbstract<T,R,S,U> {
                 unmount={this.unmount.bind(this)}
                 fullScreen={this.fullScreenFlag}
                 structureViewer={this.structureViewer}
+                structureViewerBehaviourObserver={this.structureViewerBehaviourObserver}
             />);
     }
 

+ 2 - 0
src/RcsbFv3D/RcsbFv3DAssembly.tsx

@@ -16,6 +16,7 @@ import {
 import {
     AssemblyCallbackManagerFactory
 } from "../RcsbFvSequence/SequenceViews/RcsbView/CallbackManagerFactoryImplementation/AssemblyCallbackManager";
+import {AssemblyBehaviourObserver} from "../RcsbFvStructure/StructureViewerBehaviour/AssemblyBehaviour";
 
 type RcsbFv3DAssemblyAdditionalConfig = RcsbFvAdditionalConfig & {operatorChangeCallback?:(operatorInfo: OperatorInfo)=>void};
 
@@ -75,6 +76,7 @@ export class RcsbFv3DAssembly extends RcsbFv3DAbstract<{instanceSequenceConfig?:
                 }
             },
             structureViewer: new StructureViewer<LoadMolstarInterface,{viewerElement:string|HTMLElement,viewerProps:Partial<ViewerProps>}>(new MolstarManagerFactory()),
+            structureViewerBehaviourObserver: new AssemblyBehaviourObserver<LoadMolstarInterface>(),
             cssConfig: params.cssConfig
         });
     }

+ 3 - 0
src/RcsbFv3D/RcsbFv3DComponent.tsx

@@ -18,6 +18,7 @@ import {PluginContext} from "molstar/lib/mol-plugin/context";
 import {RcsbFvSelectorManager} from "../RcsbFvState/RcsbFvSelectorManager";
 import {CSSProperties, MouseEvent} from "react";
 import {RcsbFvStateManager} from "../RcsbFvState/RcsbFvStateManager";
+import {StructureViewerBehaviourObserverInterface} from "../RcsbFvStructure/StructureViewerBehaviourInterface";
 
 export interface RcsbFv3DCssConfig {
     overwriteCss?: boolean;
@@ -35,6 +36,7 @@ export interface RcsbFv3DComponentInterface<T,R,S,U> {
     unmount:(flag:boolean)=>void;
     fullScreen: boolean;
     structureViewer: StructureViewerInterface<R,S>;
+    structureViewerBehaviourObserver: StructureViewerBehaviourObserverInterface<R>;
 }
 
 interface RcsbFv3DComponentState<T,R,S,U> {
@@ -71,6 +73,7 @@ export class RcsbFv3DComponent<T,R,S,U> extends React.Component <RcsbFv3DCompone
                             componentId={this.props.id}
                             structureViewer={this.props.structureViewer}
                             stateManager={this.stateManager}
+                            structureViewerBehaviourObserver={this.props.structureViewerBehaviourObserver}
                         />
                     </div>
                     <div style={this.sequenceCssConfig(this.props.cssConfig?.sequencePanel)}  >

+ 3 - 2
src/RcsbFv3D/RcsbFv3DCustom.tsx

@@ -1,14 +1,14 @@
 
 import {RcsbFvStructure, RcsbFvStructureConfigInterface} from "../RcsbFvStructure/RcsbFvStructure";
 import {CustomViewInterface} from "../RcsbFvSequence/SequenceViews/CustomView/CustomView";
-import {RcsbFv3DAbstract, RcsbFv3DAbstractInterface} from "./RcsbFv3DAbstract";
+import {RcsbFv3DAbstract} from "./RcsbFv3DAbstract";
 import uniqid from "uniqid";
-import {InstanceSequenceConfig} from "@rcsb/rcsb-saguaro-app/build/dist/RcsbFvWeb/RcsbFvBuilder/RcsbFvInstanceBuilder";
 import {LoadMolstarInterface} from "../RcsbFvStructure/StructureViewers/MolstarViewer/MolstarActionManager";
 import {ViewerProps} from "@rcsb/rcsb-molstar/build/src/viewer";
 import {StructureViewer} from "../RcsbFvStructure/StructureViewers/StructureViewer";
 import {MolstarManagerFactory} from "../RcsbFvStructure/StructureViewers/MolstarViewer/MolstarManagerFactory";
 import {RcsbFv3DCssConfig} from "./RcsbFv3DComponent";
+import {NullBehaviourObserver} from "../RcsbFvStructure/StructureViewerBehaviour/NullBehaviour";
 
 export interface RcsbFv3DCustomInterface  {
     elementId?: string;
@@ -40,6 +40,7 @@ export class RcsbFv3DCustom extends RcsbFv3DAbstract<{},LoadMolstarInterface,{vi
                 type:"custom",
             },
             structureViewer:new StructureViewer<LoadMolstarInterface,{viewerElement:string|HTMLElement,viewerProps:Partial<ViewerProps>}>( new MolstarManagerFactory() ),
+            structureViewerBehaviourObserver: new NullBehaviourObserver<LoadMolstarInterface>(),
             cssConfig: params.cssConfig
         });
     }

+ 2 - 0
src/RcsbFv3D/RcsbFv3DUniprot.tsx

@@ -22,6 +22,7 @@ import {
 import {RcsbFvStructure} from "../RcsbFvStructure/RcsbFvStructure";
 import {RcsbFv3DCssConfig} from "./RcsbFv3DComponent";
 import {MolstarAlignmentLoader} from "../RcsbFvStructure/StructureUtils/MolstarAlignmentLoader";
+import {UniprotBehaviourObserver} from "../RcsbFvStructure/StructureViewerBehaviour/UniprotBehaviour";
 
 export interface RcsbFv3DUniprotInterface  {
     elementId?: string;
@@ -63,6 +64,7 @@ export class RcsbFv3DUniprot extends RcsbFv3DAbstract<{upAcc:string},LoadMolstar
                 }
             },
             structureViewer: new StructureViewer<LoadMolstarInterface,{viewerElement:string|HTMLElement,viewerProps:Partial<ViewerProps>}>( new MolstarManagerFactory() ),
+            structureViewerBehaviourObserver: new UniprotBehaviourObserver<LoadMolstarInterface>()
         });
     }
 

+ 0 - 1
src/RcsbFvSequence/RcsbFvSequence.tsx

@@ -6,7 +6,6 @@ import {
 } from "../RcsbFvStructure/StructureViewerInterface";
 import {PluginContext} from "molstar/lib/mol-plugin/context";
 import {RcsbFv, RcsbFvTrackDataElementInterface} from "@rcsb/rcsb-saguaro";
-import {RcsbFvSelectorManager} from "../RcsbFvState/RcsbFvSelectorManager";
 import {RcsbView, RcsbViewInterface} from "./SequenceViews/RcsbView/RcsbView";
 import {RcsbFvStateManager} from "../RcsbFvState/RcsbFvStateManager";
 

+ 1 - 2
src/RcsbFvSequence/SequenceViews/AbstractView.tsx

@@ -4,9 +4,8 @@ import {asyncScheduler, Subscription} from "rxjs";
 
 import {RcsbFvDOMConstants} from "../../RcsbFvConstants/RcsbFvConstants";
 import {
-    SaguaroPluginModelMapType, ViewerCallbackManagerInterface, ViewerActionManagerInterface
+    ViewerCallbackManagerInterface, ViewerActionManagerInterface
 } from "../../RcsbFvStructure/StructureViewerInterface";
-import {RcsbFvSelectorManager} from "../../RcsbFvState/RcsbFvSelectorManager";
 import {SequenceViewInterface} from "./SequenceViewInterface";
 import {RcsbFvStateManager} from "../../RcsbFvState/RcsbFvStateManager";
 

+ 59 - 141
src/RcsbFvSequence/SequenceViews/RcsbView/CallbackManagerFactoryImplementation/AssemblyCallbackManager.ts

@@ -1,4 +1,5 @@
 import {
+    SaguaroChain,
     SaguaroRange,
     SaguaroRegionList
 } from "../../../../RcsbFvStructure/StructureViewerInterface";
@@ -9,6 +10,7 @@ import {
     CallbackManagerFactoryInterface,
     CallbackManagerInterface
 } from "../CallbackManagerFactoryInterface";
+import {RegionSelectionInterface} from "../../../../RcsbFvState/RcsbFvSelectorManager";
 
 export class AssemblyCallbackManagerFactory<R> implements CallbackManagerFactoryInterface<R,undefined> {
     getCallbackManager(config: CallbackConfigInterface<R>): CallbackManagerInterface<undefined> {
@@ -18,92 +20,29 @@ export class AssemblyCallbackManagerFactory<R> implements CallbackManagerFactory
 
 class AssemblyCallbackManager<R> extends AbstractCallbackManager<R,undefined> {
 
-    private readonly CREATE_COMPONENT_THR: number = 3;
-
-    public elementClickCallback(e:RcsbFvTrackDataElementInterface): void {
+    public featureClickCallback(e:RcsbFvTrackDataElementInterface): void {
         this.plugin.clearFocus();
-        this.removeComponent();
-        if(e == null)
+        if(e == null){
+            this.stateManager.selectionState.setLastSelection(null);
             return;
-
+        }
         const x = e.begin;
         const y = e.end ?? e.begin;
         const modelId: string = this.stateManager.assemblyModelSate.getString("modelId");
         const labelAsymId: string = this.stateManager.assemblyModelSate.getString("labelAsymId");
         const operatorName: string|undefined = this.stateManager.assemblyModelSate.getOperator()?.name;
-
-        if(e.isEmpty){
-            this.plugin.cameraFocus(modelId, labelAsymId, [x,y], operatorName);
-            this.selectedComponentId = labelAsymId +":"+ ((x === y) ? x.toString() : x.toString()+","+y.toString());
-            asyncScheduler.schedule(async ()=>{
-                await this.plugin.createComponent(
-                    this.selectedComponentId!,
-                    [
-                        {modelId, labelAsymId, operatorName, position: x},
-                        {modelId, labelAsymId, operatorName, position: y}
-                    ],
-                    'ball-and-stick'
-                )
-                if(x === y)
-                    asyncScheduler.schedule(()=>{
-                        this.plugin.setFocus(modelId, labelAsymId, x, y, operatorName);
-                    },60);
-            },30);
-
-        }else{
-            this.plugin.cameraFocus(modelId, labelAsymId, x, y, operatorName);
-            if((y-x)<this.CREATE_COMPONENT_THR){
-                this.selectedComponentId = labelAsymId +":"+ (x === y ? x.toString() : x.toString()+"-"+y.toString());
-                asyncScheduler.schedule(async ()=>{
-                    await this.plugin.createComponent(
-                        this.selectedComponentId!,
-                        processGaps(modelId, labelAsymId, e, operatorName),
-                        'ball-and-stick'
-                    )
-                    if(x === y)
-                        asyncScheduler.schedule(()=>{
-                            this.plugin.setFocus(modelId, labelAsymId, x, y, operatorName);
-                        },60);
-                },30);
-            }
-        }
+        if(e.isEmpty)
+            this.stateManager.selectionState.setLastSelection({modelId,labelAsymId,operatorName,source:"sequence",regions:[
+                {begin:x,end:x,source:"sequence"},
+                {begin:y,end:y,source:"sequence"}
+            ]});
+        else
+            this.stateManager.selectionState.setLastSelection({modelId,labelAsymId,operatorName,source:"sequence",regions:processGaps(modelId, labelAsymId, e, operatorName).map(r=>({...r, source:"sequence"}))});
+        this.stateManager.next({type:"feature-click", view:"1d-view"});
     }
 
     public highlightHoverCallback(selection: RcsbFvTrackDataElementInterface[]): void {
-        const modelId: string = this.stateManager.assemblyModelSate.getString("modelId");
-        const labelAsymId: string = this.stateManager.assemblyModelSate.getString("labelAsymId");
-        const operatorName: string|undefined = this.stateManager.assemblyModelSate.getOperator()?.name;
-
-        if(selection != null && selection.length > 0) {
-            if(selection[0].isEmpty){
-                const selectionList = [{
-                    modelId,
-                    labelAsymId,
-                    position: selection[0].begin,
-                    operatorName
-                }];
-                if(selection[0].end != null)
-                    selectionList.push({
-                        modelId,
-                        labelAsymId,
-                        position: selection[0].end,
-                        operatorName
-                    })
-                this.plugin.select(
-                    selectionList,
-                    'hover',
-                    'set'
-                );
-            }else {
-                this.plugin.select(
-                    processMultipleGaps(modelId, labelAsymId, selection, operatorName),
-                    'hover',
-                    'set'
-                );
-            }
-        }else{
-            this.plugin.clearSelection('hover');
-        }
+        this.select(selection, "hover","set");
     }
 
     public async modelChangeCallback(defaultAuthId?: string, defaultOperatorName?:string): Promise<void> {
@@ -111,22 +50,19 @@ class AssemblyCallbackManager<R> extends AbstractCallbackManager<R,undefined> {
     }
 
     public async pfvChangeCallback(): Promise<void>{
-        this.resetPluginView();
-        await this.pluginSelectCallback("select");
+        this.stateManager.selectionState.setLastSelection(null);
+        await this.structureViewerSelectionCallback("select");
     }
 
-    protected async innerPluginSelect(mode:'select'|'hover'): Promise<void> {
+    protected async innerStructureViewerSelectionChange(mode:'select'|'hover'): Promise<void> {
         const allSel: Array<SaguaroRegionList> | undefined = this.stateManager.selectionState.getSelection(mode);
-        const lastSel: SaguaroRegionList|null = this.stateManager.selectionState.getLastSelection('select');
+        const lastSel: SaguaroRegionList|null = this.stateManager.selectionState.getLastSelection();
         const modelId: string = this.stateManager.assemblyModelSate.getString("modelId");
         const labelAsymId: string = this.stateManager.assemblyModelSate.getString("labelAsymId");
         const operatorName: string|undefined = this.stateManager.assemblyModelSate.getOperator()?.name;
 
-        if(mode === 'select') this.removeComponent();
-
         if(allSel == null || allSel.length ===0) {
             this.rcsbFvContainer.get()?.getFv().clearSelection(mode);
-            if(mode === 'select') this.resetPluginView();
         }else if( mode === 'select' && lastSel?.labelAsymId && (lastSel?.labelAsymId != labelAsymId || lastSel?.operatorName != operatorName) ){
             const authId: string | undefined = this.stateManager.assemblyModelSate.getChainInfo(lastSel?.labelAsymId!)?.auth;
             await this.modelChangeCallback(authId, lastSel?.operatorName);
@@ -139,85 +75,67 @@ class AssemblyCallbackManager<R> extends AbstractCallbackManager<R,undefined> {
             );
             if (sel == null) {
                 this.rcsbFvContainer.get()?.getFv().clearSelection(mode);
-                if(mode === 'select') this.resetPluginView();
             } else {
                 this.rcsbFvContainer.get()?.getFv().setSelection({elements: sel.regions, mode: mode});
             }
         }
     }
 
-    protected innerSelectionChange(selection: Array<RcsbFvTrackDataElementInterface>): void {
-        const modelId: string = this.stateManager.assemblyModelSate.getString("modelId");
-        const labelAsymId: string = this.stateManager.assemblyModelSate.getString("labelAsymId");
-        const operatorName: string|undefined = this.stateManager.assemblyModelSate.getOperator()?.name;
-
-        this.plugin.clearSelection('select', {modelId, labelAsymId, operatorName});
-        this.stateManager.selectionState.clearSelection('select', {labelAsymId, operatorName});
-        if(selection == null || selection.length === 0) {
-            this.resetPluginView();
-        }else{
-            this.select(selection);
-        }
+    protected innerPfvSelectionChange(selection: Array<RcsbFvTrackDataElementInterface>): void {
+        if(selection.length == 0 && this.stateManager.selectionState.getLastSelection() != null)
+            return;
+        this.select(selection, "select", "set");
     }
 
-    private select(selection: Array<RcsbFvTrackDataElementInterface>): void{
+    private select(selection: Array<RcsbFvTrackDataElementInterface>, mode:'select'|'hover', operator: 'add'|'set'): void{
         const modelId: string = this.stateManager.assemblyModelSate.getString("modelId");
         const labelAsymId: string = this.stateManager.assemblyModelSate.getString("labelAsymId");
         const operatorName: string|undefined = this.stateManager.assemblyModelSate.getOperator()?.name;
 
-        selection.forEach(e=>{
-            const x = e.begin;
-            const y = e.end ?? e.begin;
-            if(e.isEmpty){
-                this.plugin.select([{
+        if(Array.isArray(selection) && selection.length > 0) {
+            const regions: {modelId: string, labelAsymId: string, region: RegionSelectionInterface, operatorName?: string}[] = [];
+            selection.forEach(s=>{
+                if(s.isEmpty){
+                    regions.push({
                         modelId,
                         labelAsymId,
                         operatorName,
-                        position: x
-                    }, {
+                        region: {
+                            begin: s.begin,
+                            end: s.begin,
+                            source: "sequence"
+                        }
+                    });
+                    regions.push({
                         modelId,
                         labelAsymId,
                         operatorName,
-                        position: y
-                    }],
-                    'select',
-                    'add'
-                );
-                this.stateManager.selectionState.addSelectionFromRegion(
-                    modelId,
-                    labelAsymId,
-                    {begin:x, end:y, isEmpty: true, source: 'sequence'},
-                    'select',
-                    operatorName
-                );
-            }else{
-                const ranges: SaguaroRange[] = processGaps(modelId, labelAsymId, e, operatorName)
-                this.plugin.select(ranges, 'select', 'add');
-                ranges.forEach(r=>this.stateManager.selectionState.addSelectionFromRegion(modelId, labelAsymId, {begin:r.begin, end:r.end, source: 'sequence'}, 'select', operatorName))
-            }
-        });
-    }
-
-    private removeComponent(): void {
-        if(this.selectedComponentId != null) {
-            this.plugin.removeComponent(this.selectedComponentId);
-            this.selectedComponentId = undefined;
+                        region: {
+                            begin: s.end!,
+                            end: s.end!,
+                            source: "sequence"
+                        }
+                    });
+                }else{
+                   regions.push({
+                       modelId,
+                       labelAsymId,
+                       operatorName,
+                       region: {
+                           begin: s.begin,
+                           end: s.end ?? s.begin,
+                           source: "sequence"
+                       }
+                   });
+                }
+            })
+            this.stateManager.selectionState.selectFromMultipleRegions("set", regions, mode)
+        }else {
+            this.stateManager.selectionState.clearSelection(mode, {modelId, labelAsymId, operatorName});
         }
+        this.stateManager.next({type: mode == "select" ? "selection-change" : "hover-change", view:"1d-view"});
     }
 
-    private resetPluginView(): void {
-        this.plugin.clearFocus();
-        this.plugin.resetCamera();
-    }
-
-}
-
-function processMultipleGaps(modelId: string, labelAsymId: string, list: Array<RcsbFvTrackDataElementInterface>, operatorName?:string): Array<SaguaroRange>{
-    let regions: Array<SaguaroRange> = new Array<SaguaroRange>();
-    list.forEach(e=>{
-        regions = regions.concat(processGaps(modelId, labelAsymId, e, operatorName));
-    });
-    return regions;
 }
 
 function processGaps(modelId: string, labelAsymId: string, e: RcsbFvTrackDataElementInterface, operatorName?:string): Array<SaguaroRange>{

+ 3 - 3
src/RcsbFvSequence/SequenceViews/RcsbView/CallbackManagerFactoryImplementation/UniprotCallbackManager.ts

@@ -30,7 +30,7 @@ class UniprotCallbackManager<R>  extends AbstractCallbackManager<R,{context: Uni
         this.loadParamRequest = config.loadParamRequest;
     }
 
-    elementClickCallback(e: RcsbFvTrackDataElementInterface): void {
+    featureClickCallback(e: RcsbFvTrackDataElementInterface): void {
     }
 
     highlightHoverCallback(selection: RcsbFvTrackDataElementInterface[]): void {
@@ -46,11 +46,11 @@ class UniprotCallbackManager<R>  extends AbstractCallbackManager<R,{context: Uni
         return Promise.resolve(undefined);
     }
 
-    protected innerPluginSelect(mode: "select" | "hover"): Promise<void> {
+    protected innerStructureViewerSelectionChange(mode: "select" | "hover"): Promise<void> {
         return Promise.resolve(undefined);
     }
 
-    protected innerSelectionChange(selection: Array<RcsbFvTrackDataElementInterface>): void {
+    protected innerPfvSelectionChange(selection: Array<RcsbFvTrackDataElementInterface>): void {
     }
 
 }

+ 10 - 11
src/RcsbFvSequence/SequenceViews/RcsbView/CallbackManagerFactoryInterface.ts

@@ -10,10 +10,10 @@ import {PfvManagerInterface} from "./PfvManagerFactoryInterface";
 import {RcsbFvStateManager} from "../../../RcsbFvState/RcsbFvStateManager";
 
 export interface CallbackManagerInterface<U> {
-    pluginSelectCallback(mode:'select'|'hover'): Promise<void>;
-    elementClickCallback(e:RcsbFvTrackDataElementInterface): void;
+    structureViewerSelectionCallback(mode:'select'|'hover'): Promise<void>;
+    featureClickCallback(e:RcsbFvTrackDataElementInterface): void;
     highlightHoverCallback(selection: RcsbFvTrackDataElementInterface[]): void;
-    selectionChangeCallback(selection: Array<RcsbFvTrackDataElementInterface>): void;
+    pfvSelectionChangeCallback(selection: Array<RcsbFvTrackDataElementInterface>): void;
     modelChangeCallback(defaultAuthId?: string, defaultOperatorName?:string): Promise<void>;
     pfvChangeCallback(args:U): Promise<void>;
 }
@@ -32,7 +32,6 @@ export interface CallbackConfigInterface<R> {
 export abstract class AbstractCallbackManager<R,U> implements CallbackManagerInterface<U> {
     protected readonly rcsbFvContainer: DataContainer<RcsbFvModulePublicInterface>;
     protected readonly stateManager: RcsbFvStateManager;
-    protected selectedComponentId: string|undefined;
     protected readonly plugin: ViewerCallbackManagerInterface & ViewerActionManagerInterface<R>;
     protected pfvFactory: PfvManagerInterface;
     protected readonly isInnerSelection: DataContainer<boolean> = new DataContainer<boolean>();
@@ -44,25 +43,25 @@ export abstract class AbstractCallbackManager<R,U> implements CallbackManagerInt
         this.pfvFactory = config.pfvFactory;
     }
 
-    public async pluginSelectCallback(mode:'select'|'hover'): Promise<void> {
+    public async structureViewerSelectionCallback(mode:'select'|'hover'): Promise<void> {
         if(this.rcsbFvContainer.get() == null)
             return;
         this.isInnerSelection.set(true);
-        await this.innerPluginSelect(mode);
+        await this.innerStructureViewerSelectionChange(mode);
         this.isInnerSelection.set(false);
     }
 
-    public selectionChangeCallback(selection: Array<RcsbFvTrackDataElementInterface>): void {
+    public pfvSelectionChangeCallback(selection: Array<RcsbFvTrackDataElementInterface>): void {
         if(this.isInnerSelection.get())
             return;
-        this.innerSelectionChange(selection);
+        this.innerPfvSelectionChange(selection);
     }
 
-    public abstract elementClickCallback(e:RcsbFvTrackDataElementInterface): void;
+    public abstract featureClickCallback(e:RcsbFvTrackDataElementInterface): void;
     public abstract highlightHoverCallback(selection: RcsbFvTrackDataElementInterface[]): void;
     public abstract modelChangeCallback(defaultAuthId?: string, defaultOperatorName?:string): Promise<void>;
     public abstract pfvChangeCallback(args: U): Promise<void>;
-    protected abstract innerPluginSelect(mode: "select" | "hover"): Promise<void> ;
-    protected abstract innerSelectionChange(selection: Array<RcsbFvTrackDataElementInterface>): void;
+    protected abstract innerStructureViewerSelectionChange(mode: "select" | "hover"): Promise<void> ;
+    protected abstract innerPfvSelectionChange(selection: Array<RcsbFvTrackDataElementInterface>): void;
 
 }

+ 0 - 1
src/RcsbFvSequence/SequenceViews/RcsbView/PfvManagerFactoryImplementation/AssemblyPfvManagerFactory.tsx

@@ -65,7 +65,6 @@ class AssemblyPfvManager<R> extends AbstractPfvManager<{instanceSequenceConfig:
             onChangeCallback.set(v.entryId,(x)=>{
                 this.stateManager.assemblyModelSate.set({entryId: v.entryId, labelAsymId: x.asymId, modelId: k});
                 asyncScheduler.schedule(()=>{
-                    this.stateManager.selectionState.setLastSelection('select', null);
                     this.pfvChangeCallback(undefined);
                 },100);
             });

+ 4 - 5
src/RcsbFvSequence/SequenceViews/RcsbView/RcsbView.tsx

@@ -4,9 +4,8 @@ import {RcsbFvDOMConstants} from "../../../RcsbFvConstants/RcsbFvConstants";
 import {unmount} from "@rcsb/rcsb-saguaro-app";
 import {AbstractView, AbstractViewInterface} from "../AbstractView";
 import {RcsbFvBoardConfigInterface, RcsbFvTrackDataElementInterface} from "@rcsb/rcsb-saguaro";
-import {OperatorInfo, SaguaroPluginModelMapType} from "../../../RcsbFvStructure/StructureViewerInterface";
+import {OperatorInfo} from "../../../RcsbFvStructure/StructureViewerInterface";
 import {RcsbFvAdditionalConfig, RcsbFvModulePublicInterface} from "@rcsb/rcsb-saguaro-app/build/dist/RcsbFvWeb/RcsbFvModule/RcsbFvModuleInterface";
-import {AssemblyModelSate} from "../../../RcsbFvState/AssemblyModelSate";
 import {DataContainer} from "../../../Utils/DataContainer";
 import {
     PfvManagerInterface,
@@ -140,7 +139,7 @@ export class RcsbView<T,R,U> extends AbstractView<RcsbViewInterface<T,R,U>, {},
     }
 
     private async pluginSelectCallback(mode:'select'|'hover'): Promise<void> {
-        await this.callbackManager.pluginSelectCallback(mode);
+        await this.callbackManager.structureViewerSelectionCallback(mode);
     }
 
     private async pfvChangeCallback(context: U): Promise<void>{
@@ -152,11 +151,11 @@ export class RcsbView<T,R,U> extends AbstractView<RcsbViewInterface<T,R,U>, {},
     }
 
     private selectionChangeCallback(selection: Array<RcsbFvTrackDataElementInterface>): void {
-        this.callbackManager.selectionChangeCallback(selection);
+        this.callbackManager.pfvSelectionChangeCallback(selection);
     }
 
     private elementClickCallback(e:RcsbFvTrackDataElementInterface): void {
-        this.callbackManager.elementClickCallback(e);
+        this.callbackManager.featureClickCallback(e);
     }
 
 }

+ 13 - 9
src/RcsbFvState/RcsbFvSelectorManager.ts

@@ -11,16 +11,17 @@ export interface RegionSelectionInterface{
 //TODO Check how lastSelection is used. It is not linked to selection. Only label asymId is used when the value is got
 export class RcsbFvSelectorManager {
 
-    private lastSelection: SaguaroRegionList | null = null;
+    private lastSelection: SaguaroRegionList & {source:RegionSelectionInterface["source"]}| null = null;
     private selection: Array<SaguaroRegionList> = new Array<SaguaroRegionList>();
     private hover: Array<SaguaroRegionList> = new Array<SaguaroRegionList>();
 
-    public setSelectionFromRegion(modelId: string, labelAsymId: string, region: RegionSelectionInterface, mode:'select'|'hover', operatorName?: string): void {
-        this.clearSelection(mode);
+    public selectFromRegion(operation:"set"|"add", modelId: string, labelAsymId: string, region: RegionSelectionInterface, mode:'select'|'hover', operatorName?: string): void {
+        if(operation == "set")
+            this.clearSelection(mode,{modelId,labelAsymId,operatorName});
         this.addSelectionFromRegion(modelId, labelAsymId, region, mode, operatorName);
     }
 
-    public addSelectionFromRegion(modelId: string, labelAsymId: string, region: RegionSelectionInterface, mode:'select'|'hover', operatorName?: string): void {
+    private addSelectionFromRegion(modelId: string, labelAsymId: string, region: RegionSelectionInterface, mode:'select'|'hover', operatorName?: string): void {
         if(mode === 'select'){
             this.selection.push({modelId:modelId, labelAsymId:labelAsymId, regions:[region], operatorName: operatorName});
         }else{
@@ -28,12 +29,15 @@ export class RcsbFvSelectorManager {
         }
     }
 
-    public setSelectionFromMultipleRegions(regions: {modelId: string, labelAsymId: string, region: RegionSelectionInterface, operatorName?: string}[], mode:'select'|'hover'): void {
-        this.clearSelection(mode);
+    public selectFromMultipleRegions(operation:"set"|"add", regions: {modelId: string, labelAsymId: string, region: RegionSelectionInterface, operatorName?: string}[], mode:'select'|'hover'): void {
+        if(operation == "set")
+            regions.forEach(r =>{
+                this.clearSelection(mode,r);
+            });
         this.addSelectionFromMultipleRegions(regions, mode);
     }
 
-    public addSelectionFromMultipleRegions(regions: (SaguaroChain & {region: RegionSelectionInterface})[], mode:'select'|'hover'): void {
+    private addSelectionFromMultipleRegions(regions: (SaguaroChain & {region: RegionSelectionInterface})[], mode:'select'|'hover'): void {
         regions.forEach(r=>{
             this.addSelectionFromRegion(r.modelId, r.labelAsymId, r.region, mode, r.operatorName);
         });
@@ -54,11 +58,11 @@ export class RcsbFvSelectorManager {
             return this.hover;
     }
 
-    public getLastSelection(mode:'select'|'hover'): SaguaroRegionList | null{
+    public getLastSelection(): SaguaroRegionList & {source:RegionSelectionInterface["source"]} | null{
        return this.lastSelection;
     }
 
-    public setLastSelection(mode:'select'|'hover', selection: SaguaroRegionList | null): void {
+    public setLastSelection(selection: SaguaroRegionList & {source:RegionSelectionInterface["source"]} | null): void {
         this.lastSelection = selection;
     }
 

+ 1 - 1
src/RcsbFvState/RcsbFvStateInterface.ts

@@ -9,7 +9,7 @@ import {AssemblyModelSate} from "./AssemblyModelSate";
 import {Subscription} from "rxjs";
 
 export type RcsbFvStateType = {
-    type: "selection-change"|"hover-change"|"model-change"|"representation-change";
+    type: "feature-click"|"selection-change"|"hover-change"|"model-change"|"representation-change";
     view: "1d-view" | "3d-view"
 };
 

+ 14 - 1
src/RcsbFvStructure/RcsbFvStructure.tsx

@@ -3,13 +3,21 @@ import {StructureViewerInterface} from "./StructureViewerInterface";
 import {RcsbFvDOMConstants} from "../RcsbFvConstants/RcsbFvConstants";
 import {RcsbFvSelectorManager} from "../RcsbFvState/RcsbFvSelectorManager";
 import {RcsbFvStateManager} from "../RcsbFvState/RcsbFvStateManager";
+import {StructureViewerBehaviourObserverInterface} from "./StructureViewerBehaviourInterface";
 
 export interface RcsbFvStructureConfigInterface<R,S> {
     loadConfig: R | Array<R>;
     structureViewerConfig: S;
 }
 
-export class RcsbFvStructure<R,S> extends React.Component <RcsbFvStructureConfigInterface<R,S> & {structureViewer: StructureViewerInterface<R,S>, componentId: string,  stateManager: RcsbFvStateManager}, RcsbFvStructureConfigInterface<R,S> > {
+interface RcsbFvStructureAdditionalInterface<R,S>{
+    componentId: string;
+    structureViewer: StructureViewerInterface<R,S>;
+    stateManager: RcsbFvStateManager;
+    structureViewerBehaviourObserver: StructureViewerBehaviourObserverInterface<R>;
+}
+
+export class RcsbFvStructure<R,S> extends React.Component <RcsbFvStructureConfigInterface<R,S> & RcsbFvStructureAdditionalInterface<R,S>, RcsbFvStructureConfigInterface<R,S> > {
 
     render():JSX.Element {
         return (
@@ -22,11 +30,16 @@ export class RcsbFvStructure<R,S> extends React.Component <RcsbFvStructureConfig
     async componentDidMount() {
         this.updateDimensions();
         this.props.structureViewer.init(this.props.stateManager, this.props.structureViewerConfig);
+        this.props.structureViewerBehaviourObserver.observe(this.props.structureViewer, this.props.stateManager);
         if(this.props.loadConfig)
             await this.props.structureViewer.load(this.props.loadConfig);
         window.addEventListener('resize', this.updateDimensions.bind(this));
     }
 
+    public componentWillUnmount(): void {
+        this.props.structureViewerBehaviourObserver.unsubscribe();
+    }
+
     private updateDimensions(): void {
         const div: HTMLElement | undefined | null = document.getElementById(this.props.componentId+"_"+RcsbFvDOMConstants.MOLSTAR_DIV)?.parentElement;
         if(div == null)

+ 139 - 0
src/RcsbFvStructure/StructureViewerBehaviour/AssemblyBehaviour.ts

@@ -0,0 +1,139 @@
+/*
+* Copyright (c) 2021 RCSB PDB and contributors, licensed under MIT, See LICENSE file for more info.
+* @author Joan Segura Mora <joan.segura@rcsb.org>
+*/
+
+import {
+    StructureViewerBehaviourInterface,
+    StructureViewerBehaviourObserverInterface
+} from "../StructureViewerBehaviourInterface";
+import {
+    SaguaroRange,
+    SaguaroRegionList,
+    ViewerActionManagerInterface,
+    ViewerCallbackManagerInterface
+} from "../StructureViewerInterface";
+import {RcsbFvStateInterface} from "../../RcsbFvState/RcsbFvStateInterface";
+import {asyncScheduler, Subscription} from "rxjs";
+
+export class AssemblyBehaviourObserver<R> implements StructureViewerBehaviourObserverInterface<R> {
+
+    private structureBehaviour: StructureViewerBehaviourInterface;
+    public observe(structureViewer: ViewerCallbackManagerInterface & ViewerActionManagerInterface<R>, stateManager: RcsbFvStateInterface): void {
+        this.structureBehaviour = new AssemblyBehaviour(structureViewer, stateManager);
+    }
+
+    public unsubscribe(): void {
+        this.structureBehaviour.unsubscribe();
+    }
+
+}
+
+class AssemblyBehaviour<R> implements StructureViewerBehaviourInterface {
+
+    private readonly structureViewer: ViewerCallbackManagerInterface & ViewerActionManagerInterface<R>;
+    private readonly stateManager: RcsbFvStateInterface;
+    private readonly subscription: Subscription;
+    private selectedComponentId: string|undefined;
+    private readonly CREATE_COMPONENT_THR: number = 3;
+
+    constructor(structureViewer: ViewerCallbackManagerInterface & ViewerActionManagerInterface<R>, stateManager: RcsbFvStateInterface) {
+        this.structureViewer = structureViewer;
+        this.stateManager = stateManager;
+        this.subscription = this.subscribe();
+    }
+
+    private subscribe(): Subscription {
+        return this.stateManager.subscribe(async o=>{
+            if(o.type == "selection-change" && o.view == "1d-view")
+                this.selectionChange();
+            if(o.type == "hover-change" && o.view == "1d-view")
+                this.hoverChange();
+            if(o.type == "feature-click" && o.view == "1d-view")
+                await this.featureClick();
+            if(o.type == "selection-change" && o.view == "3d-view")
+                await this.isSelectionEmpty();
+        });
+    }
+
+    public selectionChange(): void {
+        this.select("select", "set");
+    }
+
+    public hoverChange(): void {
+        this.select("hover", "set");
+    }
+
+    public async featureClick(): Promise<void> {
+        await this.removeComponent();
+        const {modelId, labelAsymId, operatorName, regions} = this.stateManager.selectionState.getLastSelection() ?? {};
+        if(modelId && labelAsymId && operatorName && Array.isArray(regions) && regions.length > 0) {
+            const residues: number[] = regions.map(r=> r.begin == r.end ? [r.begin] : [r.begin,r.end]).flat().filter(r=>r!=null);
+            if(residues.length == 0)
+                return;
+            const ranges: SaguaroRange[] = regions.map(r=>({
+                modelId,
+                labelAsymId,
+                operatorName,
+                begin: r.begin,
+                end: r.end
+            }));
+            this.structureViewer.cameraFocus(modelId, labelAsymId, residues, operatorName);
+            const nRes = ranges.map(r=>r.end-r.begin+1).reduce((prev,curr)=>curr+prev,0);
+            if( nRes <= this.CREATE_COMPONENT_THR)
+                asyncScheduler.schedule(async ()=>{
+                    const x = residues[0];
+                    const y = residues[residues.length-1];
+                    this.selectedComponentId = labelAsymId +":"+ ((x === y) ? x.toString() : x.toString()+","+y.toString());
+                    await this.structureViewer.createComponent(this.selectedComponentId!,ranges, "ball-and-stick");
+                    if(nRes == 1)
+                        this.structureViewer.setFocus(modelId,labelAsymId,residues[0],residues[0],operatorName);
+                });
+            else
+                this.selectedComponentId = undefined;
+        }
+    }
+
+    public unsubscribe(): void {
+        this.subscription.unsubscribe();
+    }
+
+    private select(mode:'select'|'hover', operator: 'add'|'set'): void {
+        const modelId: string = this.stateManager.assemblyModelSate.getString("modelId");
+        const labelAsymId: string = this.stateManager.assemblyModelSate.getString("labelAsymId");
+        const operatorName: string|undefined = this.stateManager.assemblyModelSate.getOperator()?.name;
+        const selection: SaguaroRegionList|undefined = this.stateManager.selectionState.getSelectionWithCondition(modelId, labelAsymId, mode, operatorName);
+        if(operator == "set")
+            this.structureViewer.clearSelection(mode, {modelId, labelAsymId, operatorName});
+        if(selection && Array.isArray(selection.regions) && selection.regions.length > 0) {
+            this.structureViewer.select(selection.regions.map(r=>({
+                modelId,
+                labelAsymId,
+                operatorName,
+                begin:r.begin,
+                end:r.end
+            })), mode, "add");
+        } else {
+            if(mode == "select") this.resetPluginView();
+        }
+    }
+
+    private async isSelectionEmpty(): Promise<void> {
+        if(this.stateManager.selectionState.getLastSelection() == null) {
+            await this.removeComponent();
+            this.resetPluginView();
+        }
+    }
+
+    private async removeComponent(): Promise<void> {
+        if(typeof this.selectedComponentId === "string")
+            await this.structureViewer.removeComponent(this.selectedComponentId);
+    }
+
+
+    private resetPluginView(): void {
+        this.structureViewer.clearFocus();
+        this.structureViewer.resetCamera();
+    }
+
+}

+ 19 - 0
src/RcsbFvStructure/StructureViewerBehaviour/NullBehaviour.ts

@@ -0,0 +1,19 @@
+/*
+* Copyright (c) 2021 RCSB PDB and contributors, licensed under MIT, See LICENSE file for more info.
+* @author Joan Segura Mora <joan.segura@rcsb.org>
+*/
+
+import {StructureViewerBehaviourObserverInterface} from "../StructureViewerBehaviourInterface";
+import {ViewerActionManagerInterface, ViewerCallbackManagerInterface} from "../StructureViewerInterface";
+import {RcsbFvStateInterface} from "../../RcsbFvState/RcsbFvStateInterface";
+
+export class NullBehaviourObserver<R> implements StructureViewerBehaviourObserverInterface<R> {
+
+    observe(structureViewer: ViewerCallbackManagerInterface & ViewerActionManagerInterface<R>, stateManager: RcsbFvStateInterface): void {
+    }
+
+    unsubscribe(): void {
+    }
+
+}
+

+ 18 - 0
src/RcsbFvStructure/StructureViewerBehaviour/UniprotBehaviour.ts

@@ -0,0 +1,18 @@
+/*
+* Copyright (c) 2021 RCSB PDB and contributors, licensed under MIT, See LICENSE file for more info.
+* @author Joan Segura Mora <joan.segura@rcsb.org>
+*/
+
+import {StructureViewerBehaviourObserverInterface} from "../StructureViewerBehaviourInterface";
+import {ViewerActionManagerInterface, ViewerCallbackManagerInterface} from "../StructureViewerInterface";
+import {RcsbFvStateInterface} from "../../RcsbFvState/RcsbFvStateInterface";
+
+export class UniprotBehaviourObserver <R> implements StructureViewerBehaviourObserverInterface<R> {
+
+    observe(structureViewer: ViewerCallbackManagerInterface & ViewerActionManagerInterface<R>, stateManager: RcsbFvStateInterface): void {
+    }
+
+    unsubscribe(): void {
+    }
+
+}

+ 19 - 0
src/RcsbFvStructure/StructureViewerBehaviourInterface.ts

@@ -0,0 +1,19 @@
+/*
+* Copyright (c) 2021 RCSB PDB and contributors, licensed under MIT, See LICENSE file for more info.
+* @author Joan Segura Mora <joan.segura@rcsb.org>
+*/
+
+import {ViewerActionManagerInterface, ViewerCallbackManagerInterface} from "./StructureViewerInterface";
+import {RcsbFvStateInterface} from "../RcsbFvState/RcsbFvStateInterface";
+
+export interface StructureViewerBehaviourObserverInterface<R> {
+    observe(structureViewer: ViewerCallbackManagerInterface & ViewerActionManagerInterface<R>, stateManager: RcsbFvStateInterface): void;
+    unsubscribe(): void;
+}
+
+export interface StructureViewerBehaviourInterface {
+    selectionChange(): void;
+    hoverChange(): void;
+    featureClick(): void;
+    unsubscribe(): void;
+}

+ 1 - 1
src/RcsbFvStructure/StructureViewerInterface.ts

@@ -1,7 +1,7 @@
 import {PluginContext} from "molstar/lib/mol-plugin/context";
 import {StructureRepresentationRegistry} from "molstar/lib/mol-repr/structure/registry";
 import {ColorTheme} from "molstar/lib/mol-theme/color";
-import {RcsbFvSelectorManager, RegionSelectionInterface} from "../RcsbFvState/RcsbFvSelectorManager";
+import {RegionSelectionInterface} from "../RcsbFvState/RcsbFvSelectorManager";
 import {RcsbFvStateManager} from "../RcsbFvState/RcsbFvStateManager";
 import {Subscription} from "rxjs";
 

+ 10 - 4
src/RcsbFvStructure/StructureViewers/MolstarViewer/MolstarCallbackManager.ts

@@ -111,26 +111,30 @@ export class MolstarCallbackManager implements ViewerCallbackManagerInterface{
                         for (const e of surroundingsLoci.elements) {
                             StructureElement.Location.set(surroundingsLoc, surroundingsLoci.structure, e.unit, e.unit.elements[0]);
                             if(SP.entity.type(surroundingsLoc) === 'polymer'){
-                                this.stateManager.selectionState.setLastSelection('select', {
+                                this.stateManager.selectionState.setLastSelection({
                                     modelId: currentModelId,
                                     labelAsymId: SP.chain.label_asym_id(surroundingsLoc),
+                                    source:"structure",
                                     regions: []
                                 });
                             }
                         }
                         this.innerSelectionFlag.set(false);
                     }else if( SP.entity.type(loc) === 'polymer' ) {
-                        this.stateManager.selectionState.setLastSelection('select', {
+                        this.stateManager.selectionState.setLastSelection({
                             modelId: currentModelId,
                             labelAsymId: SP.chain.label_asym_id(loc),
                             operatorName: SP.unit.operator_name(loc),
+                            source:"structure",
                             regions: []
                         });
                     }else{
-                        this.stateManager.selectionState.setLastSelection('select', null);
+                        this.stateManager.selectionState.setLastSelection(null);
                     }
+                else
+                    this.stateManager.selectionState.setLastSelection(null);
             }else{
-                this.stateManager.selectionState.setLastSelection('select', null);
+                this.stateManager.selectionState.setLastSelection(null);
             }
             const sequenceData: Array<SaguaroSet> = new Array<SaguaroSet>();
             for(const structure of this.viewer.plugin.managers.structure.hierarchy.current.structures){
@@ -157,6 +161,8 @@ export class MolstarCallbackManager implements ViewerCallbackManagerInterface{
                 }
             }
             this.stateManager.selectionState.setSelectionFromResidueSelection(sequenceData, 'select', 'structure');
+            if(sequenceData.length == 0)
+                this.stateManager.selectionState.setLastSelection(null);
             this.stateManager.next({type:"selection-change", view:"3d-view"});
         });
         return this.selectSubs;

+ 1 - 1
src/examples/structural-alignment/index.ts

@@ -126,7 +126,7 @@ const fvConfig: FeatureViewInterface<LoadMolstarInterface> = {
                 labelAsymId: "A",
                 region: {begin: alignmentManager.getTargetPosition(r.begin), end: alignmentManager.getTargetPosition(r.end ?? r.begin), source: "sequence"} as RegionSelectionInterface
             })));
-            stateManager.selectionState.addSelectionFromMultipleRegions(regions, "select");
+            stateManager.selectionState.selectFromMultipleRegions("set", regions, "select");
             plugin.select(regions.map(r => ({
                 ...r,
                 begin: r.region.begin,