bioinsilico vor 2 Jahren
Ursprung
Commit
05981b5f74

+ 7 - 0
CHANGELOG.md

@@ -2,6 +2,13 @@
 
 [Semantic Versioning](https://semver.org/)
 
+## [2.2.0-msa] - 2022-10-03
+### New Features
+- New UniProt MSA 1D3D view
+  - `UniprotPfvManagerFactory` builds the UniProt MSA PFV
+  - `UniprotCallbackManagerFactory` 1D callbacks
+  - `UniprotBehaviourObserver` 3D callbacks
+
 ## [2.1.0] - 2022-09-02
 ### Major refactoring
 - `StructureViewerBehaviourObserverInterface` factory of structure viewer behaviours

+ 7 - 7
package-lock.json

@@ -12,7 +12,7 @@
         "@rcsb/rcsb-api-tools": "^4.1.0",
         "@rcsb/rcsb-molstar": "^2.5.5",
         "@rcsb/rcsb-saguaro": "^2.2.15",
-        "@rcsb/rcsb-saguaro-app": "^4.4.9",
+        "@rcsb/rcsb-saguaro-app": "^4.4.10",
         "molstar": "^3.13.0"
       },
       "devDependencies": {
@@ -2515,9 +2515,9 @@
       }
     },
     "node_modules/@rcsb/rcsb-saguaro-app": {
-      "version": "4.4.9",
-      "resolved": "https://registry.npmjs.org/@rcsb/rcsb-saguaro-app/-/rcsb-saguaro-app-4.4.9.tgz",
-      "integrity": "sha512-sbfoLKub4xrbtIeHfEb8diSkvyeuRn7KKDZW9j0kdrGB9ObixT05ikyHhL9Cu3ofOfHcUsEpKmA/4uBzo09IcA==",
+      "version": "4.4.10",
+      "resolved": "https://registry.npmjs.org/@rcsb/rcsb-saguaro-app/-/rcsb-saguaro-app-4.4.10.tgz",
+      "integrity": "sha512-P7nccp4iSnkUY9SIdSCJYVM7+OvzOYxO+t+ZkbiohMQw1sV+a5REcNWt0YYzofQX3ZJQk6/PYs088J4Qw+yzGQ==",
       "dependencies": {
         "@rcsb/rcsb-api-tools": "^4.1.0",
         "@rcsb/rcsb-saguaro": "^2.2.15",
@@ -14088,9 +14088,9 @@
       }
     },
     "@rcsb/rcsb-saguaro-app": {
-      "version": "4.4.9",
-      "resolved": "https://registry.npmjs.org/@rcsb/rcsb-saguaro-app/-/rcsb-saguaro-app-4.4.9.tgz",
-      "integrity": "sha512-sbfoLKub4xrbtIeHfEb8diSkvyeuRn7KKDZW9j0kdrGB9ObixT05ikyHhL9Cu3ofOfHcUsEpKmA/4uBzo09IcA==",
+      "version": "4.4.10",
+      "resolved": "https://registry.npmjs.org/@rcsb/rcsb-saguaro-app/-/rcsb-saguaro-app-4.4.10.tgz",
+      "integrity": "sha512-P7nccp4iSnkUY9SIdSCJYVM7+OvzOYxO+t+ZkbiohMQw1sV+a5REcNWt0YYzofQX3ZJQk6/PYs088J4Qw+yzGQ==",
       "requires": {
         "@rcsb/rcsb-api-tools": "^4.1.0",
         "@rcsb/rcsb-saguaro": "^2.2.15",

+ 2 - 2
package.json

@@ -1,6 +1,6 @@
 {
   "name": "@rcsb/rcsb-saguaro-3d",
-  "version": "2.1.0",
+  "version": "2.2.0-uniprot-msa.1",
   "description": "RCSB Molstar/Saguaro Web App",
   "main": "build/dist/app.js",
   "files": [
@@ -83,7 +83,7 @@
     "@rcsb/rcsb-api-tools": "^4.1.0",
     "@rcsb/rcsb-molstar": "^2.5.5",
     "@rcsb/rcsb-saguaro": "^2.2.15",
-    "@rcsb/rcsb-saguaro-app": "^4.4.9",
+    "@rcsb/rcsb-saguaro-app": "^4.4.10",
     "molstar": "^3.13.0"
   },
   "bugs": {

+ 97 - 5
src/RcsbFvSequence/SequenceViews/RcsbView/CallbackManagerFactoryImplementation/UniprotCallbackManager.ts

@@ -7,6 +7,10 @@ import {RcsbFvTrackDataElementInterface} from "@rcsb/rcsb-saguaro";
 import {
     UniprotSequenceOnchangeInterface
 } from "@rcsb/rcsb-saguaro-app/build/dist/RcsbFvWeb/RcsbFvBuilder/RcsbFvUniprotBuilder";
+import {AlignmentResponse} from "@rcsb/rcsb-api-tools/build/RcsbGraphQL/Types/Borrego/GqlTypes";
+import {RegionSelectionInterface} from "../../../../RcsbFvState/RcsbFvSelectorManager";
+import {ChainInfo, SaguaroRegionList} from "../../../../RcsbFvStructure/StructureViewerInterface";
+import {TagDelimiter} from "@rcsb/rcsb-saguaro-app";
 
 export class UniprotCallbackManagerFactory<R> implements CallbackManagerFactoryInterface<R,{context: UniprotSequenceOnchangeInterface;}> {
 
@@ -20,6 +24,7 @@ export class UniprotCallbackManagerFactory<R> implements CallbackManagerFactoryI
     }
 }
 
+type SelectedRegion = {modelId: string, labelAsymId: string, region: RegionSelectionInterface, operatorName?: string};
 class UniprotCallbackManager<R>  extends AbstractCallbackManager<R,{context: UniprotSequenceOnchangeInterface;}>{
 
     private readonly loadParamRequest:(id: string)=>R;
@@ -29,10 +34,16 @@ class UniprotCallbackManager<R>  extends AbstractCallbackManager<R,{context: Uni
         this.loadParamRequest = config.loadParamRequest;
     }
 
-    featureClickCallback(e: RcsbFvTrackDataElementInterface): void {
+    async featureClickCallback(e: RcsbFvTrackDataElementInterface): Promise<void> {
+        const alignment: AlignmentResponse|undefined = await this.rcsbFvContainer.get()?.getAlignmentResponse();
+        if(alignment){
+            const regions: SelectedRegion[] = this.getModelRegions( e? [e] : [], alignment, Array.from(this.stateManager.assemblyModelSate.getMap().keys()),"query");
+            this.stateManager.next<"feature-click",SelectedRegion[]>({type:"feature-click", view:"1d-view", data: regions})
+        }
     }
 
-    highlightHoverCallback(selection: RcsbFvTrackDataElementInterface[]): void {
+    async highlightHoverCallback(selection: RcsbFvTrackDataElementInterface[]): Promise<void> {
+        await this.select(selection, "hover");
     }
 
     modelChangeCallback(defaultAuthId?: string, defaultOperatorName?: string): Promise<void> {
@@ -45,11 +56,92 @@ class UniprotCallbackManager<R>  extends AbstractCallbackManager<R,{context: Uni
         return Promise.resolve(undefined);
     }
 
-    protected innerStructureViewerSelectionChange(mode: "select" | "hover"): Promise<void> {
-        return Promise.resolve(undefined);
+    protected async innerStructureViewerSelectionChange(mode: "select" | "hover"): Promise<void> {
+        const allSel: Array<SaguaroRegionList> | undefined = this.stateManager.selectionState.getSelection(mode);
+        if(allSel == null || allSel.length ===0) {
+            this.rcsbFvContainer.get()?.getFv().clearSelection(mode);
+        }else{
+            const alignment: AlignmentResponse|undefined = await this.rcsbFvContainer.get()?.getAlignmentResponse();
+            if(alignment) {
+                allSel.forEach(sel => {
+                    const chain: ChainInfo | undefined = this.stateManager.assemblyModelSate.getModelChainInfo(sel.modelId)?.chains.find(ch => ch.entityId == TagDelimiter.parseEntity(sel.modelId).entityId && ch.label == sel.labelAsymId);
+                    if (chain) {
+                        const regions = this.getModelRegions(sel.regions.map(r => ({
+                            begin: r.begin,
+                            end: r.end
+                        })), alignment, [sel.modelId], "target");
+                        this.rcsbFvContainer.get()?.getFv().addSelection({mode, elements: regions.map(r => r.region)})
+                    }
+                });
+            }
+        }
+    }
+
+    protected async innerPfvSelectionChange(selection: Array<RcsbFvTrackDataElementInterface>): Promise<void> {
+        await this.select(selection, "select");
+    }
+
+    private async select(selection: Array<RcsbFvTrackDataElementInterface>, mode:"select"|"hover"): Promise<void> {
+        const alignment: AlignmentResponse|undefined = await this.rcsbFvContainer.get()?.getAlignmentResponse();
+        if(alignment){
+            const regions = this.getModelRegions(selection, alignment, Array.from(this.stateManager.assemblyModelSate.getMap().keys()), "query");
+            if(regions.length == 0)
+                this.stateManager.selectionState.clearSelection("select");
+            this.stateManager.selectionState.selectFromMultipleRegions("set", regions, mode);
+            this.stateManager.next({type: mode == "select" ? "selection-change" : "hover-change", view:"1d-view"});
+        }
     }
 
-    protected innerPfvSelectionChange(selection: Array<RcsbFvTrackDataElementInterface>): void {
+    private getModelRegions(selection: Array<RcsbFvTrackDataElementInterface>, alignment: AlignmentResponse, modelList: string[], pointer:"query"|"target"): SelectedRegion[] {
+        const cPointer: "query"|"target" = pointer == "query" ? "target" : "query";
+        const regions: SelectedRegion[] = [];
+        modelList.forEach(modelId=>{
+            const chain: ChainInfo|undefined = this.stateManager.assemblyModelSate.getModelChainInfo(modelId)?.chains.find(ch=>ch.entityId==TagDelimiter.parseEntity(modelId).entityId);
+            if(!chain)
+                return;
+            const labelAsymId: string | undefined = chain.label;
+            const operatorName: string | undefined = chain.operators[0].name;
+            if(!labelAsymId || ! operatorName)
+                return;
+            selection.forEach(s=>{
+                const rangeBegin = alignment.target_alignment?.find(ta=>ta?.target_id === modelId)?.aligned_regions?.find(ar=>((ar?.[alignmentPointer[pointer].begin] ?? -1) <= s.begin) && (s.begin <= (ar?.[alignmentPointer[pointer].end] ?? -1)));
+                const rangeEnd = alignment.target_alignment?.find(ta=>ta?.target_id === modelId)?.aligned_regions?.find(ar=>((ar?.[alignmentPointer[pointer].begin] ?? -1) <= (s.end ?? s.begin) ) && ((s.end ?? s.begin) <= (ar?.[alignmentPointer[pointer].end] ?? -1)));
+                const begin = s.begin - (rangeBegin?.[alignmentPointer[pointer].begin] ?? 0) + (rangeBegin?.[alignmentPointer[cPointer].begin] ?? 0);
+                const end = (s.end ?? s.begin) - (rangeEnd?.[alignmentPointer[pointer].begin] ?? 0) + (rangeEnd?.[alignmentPointer[cPointer].begin] ?? 0);
+                regions.push({
+                    modelId,
+                    labelAsymId,
+                    operatorName,
+                    region:{
+                        begin,
+                        end,
+                        source:"sequence"
+                    }
+                });
+            })
+        });
+        return regions;
     }
+}
 
+interface AlignmentPointerInterface {
+    query: {
+        begin: "query_begin",
+        end: "query_end"
+    },
+    target: {
+        begin: "target_begin",
+        end: "target_end"
+    }
+}
+
+const alignmentPointer: AlignmentPointerInterface = {
+    query: {
+        begin: "query_begin",
+        end: "query_end"
+    },
+    target: {
+        begin: "target_begin",
+        end: "target_end"
+    }
 }

+ 0 - 0
src/RcsbFvSequence/SequenceViews/RcsbView/PfvManagerFactoryImplementation/AssemblyPfvManagerFactory/ChainDisplayComponent.tsx → src/RcsbFvSequence/SequenceViews/RcsbView/PfvManagerFactoryImplementation/AssemblyPfvComponents/ChainDisplayComponent.tsx


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

@@ -12,7 +12,7 @@ import {asyncScheduler} from "rxjs";
 import {buildInstanceSequenceFv, FeatureType, RcsbFvUI, RcsbRequestContextManager} from "@rcsb/rcsb-saguaro-app";
 import {RcsbFvDOMConstants} from "../../../../RcsbFvConstants/RcsbFvConstants";
 import {SelectOptionProps} from "@rcsb/rcsb-saguaro-app/build/dist/RcsbFvWeb/RcsbFvComponents/SelectButton";
-import {ChainDisplayComponent} from "./AssemblyPfvManagerFactory/ChainDisplayComponent";
+import {ChainDisplayComponent} from "./AssemblyPfvComponents/ChainDisplayComponent";
 import * as React from "react";
 import {AnnotationFeatures, Source, Type} from "@rcsb/rcsb-api-tools/build/RcsbGraphQL/Types/Borrego/GqlTypes";
 import {

+ 12 - 3
src/RcsbFvSequence/SequenceViews/RcsbView/PfvManagerFactoryImplementation/UniprotPfvManagerFactory/UniprotRowMarkComponent.tsx → src/RcsbFvSequence/SequenceViews/RcsbView/PfvManagerFactoryImplementation/UniprotPfvComponents/UniprotRowMarkComponent.tsx

@@ -21,12 +21,13 @@ interface UniprotRowMarkInterface  {
 interface UniprotRowMarkState {
     visibility: Property.Visibility | undefined;
     borderLeftColor: Property.BorderLeftColor | undefined;
+    markHoverColor: string;
 }
 
 export class UniprotRowMarkComponent extends React.Component <UniprotRowMarkInterface,UniprotRowMarkState> {
 
     private readonly HOVER_COLOR: string = "#666";
-    private readonly ACTIVE_COLOR: string = "#ccc";
+    private readonly ACTIVE_COLOR: string ="rgb(51, 122, 183)";
     private subscription: Subscription;
 
     constructor(props:UniprotRowMarkInterface) {
@@ -35,14 +36,15 @@ export class UniprotRowMarkComponent extends React.Component <UniprotRowMarkInte
 
     readonly state: UniprotRowMarkState = {
         visibility: undefined,
-        borderLeftColor: undefined
+        borderLeftColor: undefined,
+        markHoverColor: this.HOVER_COLOR
     }
 
     public render(): JSX.Element {
         return (
             <>
                 <div onClick={this.click.bind(this)} onMouseOver={this.hover.bind(this)} style={{visibility: this.state.visibility, cursor:"pointer", display:"inline-block", width:6, height:6, marginBottom: 4, marginRight:5}} >
-                    <div className={classes.uniprotRowMark} style={{borderLeftColor: this.props.isGlowing ? this.HOVER_COLOR : (this.state.borderLeftColor)}}/>
+                    <div className={classes.uniprotRowMark} onMouseOut={()=>this.markHover(false)}onMouseOver={()=>this.markHover(true)} style={{borderLeftColor: this.props.isGlowing ? this.state.markHoverColor : (this.state.borderLeftColor)}}/>
                 </div>
             </>
         );
@@ -78,5 +80,12 @@ export class UniprotRowMarkComponent extends React.Component <UniprotRowMarkInte
         this.props.hoverCallback?.();
     }
 
+    private markHover(flag:boolean): void {
+        if(flag)
+            this.setState({markHoverColor:this.ACTIVE_COLOR});
+        else
+            this.setState({markHoverColor:this.HOVER_COLOR});
+    }
+
 
 }

+ 21 - 1
src/RcsbFvSequence/SequenceViews/RcsbView/PfvManagerFactoryImplementation/UniprotPfvManagerFactory/UniprotRowTitleCheckbox.tsx → src/RcsbFvSequence/SequenceViews/RcsbView/PfvManagerFactoryImplementation/UniprotPfvComponents/UniprotRowTitleCheckbox.tsx

@@ -35,7 +35,8 @@ export class UniprotRowTitleCheckbox extends React.Component <UniprotRowTitleChe
 
 
     public render():JSX.Element {
-        return (<input type={"checkbox"} disabled={this.props.disabled} checked={this.state.checked} onClick={()=>{this.click()}}/>);
+        //return (<input type={"checkbox"} disabled={this.props.disabled} checked={this.state.checked} onClick={()=>{this.click()}}/>);
+        return (<div style={this.style()} onClick={()=>{this.click()}}/>);
     }
 
     public componentDidUpdate(prevProps: Readonly<UniprotRowTitleCheckboxInterface>, prevState: Readonly<UniprotRowTitleCheckboxState>, snapshot?: any) {
@@ -93,6 +94,25 @@ export class UniprotRowTitleCheckbox extends React.Component <UniprotRowTitleChe
                 }
             });
         });
+    }
 
+    private style():React.CSSProperties {
+        const color = {
+            "checked":"rgb(51, 122, 183)",
+            "unchecked":"rgba(51,122,183,0.44)",
+            "disabled":"#ddd"
+        }
+        return {
+            width:7,
+            height:7,
+            backgroundColor: this.props.disabled ? color.disabled : color[ this.state.checked ? "checked" : "unchecked"],
+            border: 1,
+            borderStyle: "solid",
+            borderColor: this.props.disabled ? color.disabled :  color.checked,
+            display:"inline-block",
+            marginLeft:4,
+            cursor: this.props.disabled ? undefined : "pointer"
+        };
     }
+
 }

+ 1 - 1
src/RcsbFvSequence/SequenceViews/RcsbView/PfvManagerFactoryImplementation/UniprotPfvManagerFactory/UniprotRowTitleComponent.tsx → src/RcsbFvSequence/SequenceViews/RcsbView/PfvManagerFactoryImplementation/UniprotPfvComponents/UniprotRowTitleComponent.tsx

@@ -43,7 +43,7 @@ export class UniprotRowTitleComponent extends React.Component <UniprotRowTitleIn
 
     public render(): JSX.Element{
        return <div style={{textAlign:"right"}}>
-           {this.props.targetAlignment.target_id}
+           <a href={`/structure/${TagDelimiter.parseEntity(this.props.targetAlignment.target_id!).entryId}#entity-${TagDelimiter.parseEntity(this.props.targetAlignment.target_id!).entityId}`}>{this.props.targetAlignment.target_id}</a>
            <UniprotRowTitleCheckbox disabled={this.state.disabled} {...TagDelimiter.parseEntity(this.props.targetAlignment.target_id!)} tag={"aligned"} stateManager={this.props.stateManager}/>
            <UniprotRowTitleCheckbox disabled={this.state.disabled} {...TagDelimiter.parseEntity(this.props.targetAlignment.target_id!)} tag={"polymer"} stateManager={this.props.stateManager}/>
            <UniprotRowTitleCheckbox disabled={this.state.disabled} {...TagDelimiter.parseEntity(this.props.targetAlignment.target_id!)} tag={"non-polymer"} stateManager={this.props.stateManager}/>

+ 5 - 11
src/RcsbFvSequence/SequenceViews/RcsbView/PfvManagerFactoryImplementation/UniprotPfvManagerFactory.ts

@@ -7,8 +7,7 @@ import {
 import {
     RcsbFvModulePublicInterface
 } from "@rcsb/rcsb-saguaro-app/build/dist/RcsbFvWeb/RcsbFvModule/RcsbFvModuleInterface";
-import {buildMultipleAlignmentSequenceFv, TagDelimiter} from "@rcsb/rcsb-saguaro-app";
-import {RcsbFvDOMConstants} from "../../../../RcsbFvConstants/RcsbFvConstants";
+import {buildUniprotFv, TagDelimiter} from "@rcsb/rcsb-saguaro-app";
 import {
     UniprotSequenceOnchangeInterface
 } from "@rcsb/rcsb-saguaro-app/build/dist/RcsbFvWeb/RcsbFvBuilder/RcsbFvUniprotBuilder";
@@ -16,9 +15,8 @@ import {
     AlignmentRequestContextType
 } from "@rcsb/rcsb-saguaro-app/build/dist/RcsbFvWeb/RcsbFvFactories/RcsbFvTrackFactory/TrackFactoryImpl/AlignmentTrackFactory";
 import {AlignmentResponse, TargetAlignment} from "@rcsb/rcsb-api-tools/build/RcsbGraphQL/Types/Borrego/GqlTypes";
-import {UniprotRowTitleComponent} from "./UniprotPfvManagerFactory/UniprotRowTitleComponent";
-import {UniprotRowMarkComponent} from "./UniprotPfvManagerFactory/UniprotRowMarkComponent";
-
+import {UniprotRowTitleComponent} from "./UniprotPfvComponents/UniprotRowTitleComponent";
+import {UniprotRowMarkComponent} from "./UniprotPfvComponents/UniprotRowMarkComponent";
 
 interface UniprotPfvManagerInterface<R> extends PfvManagerFactoryConfigInterface<R,{context: UniprotSequenceOnchangeInterface;}> {
     upAcc:string;
@@ -44,16 +42,12 @@ class UniprotPfvManager<R> extends AbstractPfvManager<{upAcc:string},R,{context:
     }
 
     async create(): Promise<RcsbFvModulePublicInterface | undefined> {
-        this.module = await buildMultipleAlignmentSequenceFv(
+        this.module = await buildUniprotFv(
             this.rcsbFvDivId,
-            RcsbFvDOMConstants.SELECT_BUTTON_PFV_ID,
             this.upAcc,
             {
-                onChangeCallback:(context,module)=>{
-                    this.pfvChangeCallback({context});
-                }
-            },{
                 ... this.additionalConfig,
+                boardConfig: this.boardConfigContainer.get(),
                 trackConfigModifier: {
                     alignment: (alignmentContext: AlignmentRequestContextType, targetAlignment: TargetAlignment) => new Promise((resolve)=>{
                         resolve({

+ 109 - 9
src/RcsbFvStructure/StructureViewerBehaviour/UniprotBehaviour.ts

@@ -7,12 +7,20 @@ import {
     StructureViewerBehaviourInterface,
     StructureViewerBehaviourObserverInterface
 } from "../StructureViewerBehaviourInterface";
-import {ViewerActionManagerInterface, ViewerCallbackManagerInterface} from "../StructureViewerInterface";
+import {
+    ChainInfo,
+    OperatorInfo,
+    SaguaroRange,
+    ViewerActionManagerInterface,
+    ViewerCallbackManagerInterface
+} from "../StructureViewerInterface";
 import {RcsbFvStateInterface} from "../../RcsbFvState/RcsbFvStateInterface";
-import {Subscription} from "rxjs";
+import {asyncScheduler, Subscription} from "rxjs";
 import {StructureLoaderInterface} from "../StructureUtils/StructureLoaderInterface";
 import {TagDelimiter} from "@rcsb/rcsb-saguaro-app";
 import {createSelectionExpressions} from "@rcsb/rcsb-molstar/build/src/viewer/helpers/selection";
+import {RegionSelectionInterface} from "../../RcsbFvState/RcsbFvSelectorManager";
+import {defaultInputTarget} from "concurrently/dist/src/defaults";
 
 export class UniprotBehaviourObserver<R> implements StructureViewerBehaviourObserverInterface<R> {
 
@@ -35,12 +43,16 @@ export class UniprotBehaviourObserver<R> implements StructureViewerBehaviourObse
 
 }
 
+type SelectedRegion = {modelId: string, labelAsymId: string, region: RegionSelectionInterface, operatorName?: string};
 class UniprotBehaviour<R> implements StructureViewerBehaviourInterface {
 
     private readonly structureViewer: ViewerCallbackManagerInterface & ViewerActionManagerInterface<R>;
     private readonly stateManager: RcsbFvStateInterface;
     private readonly subscription: Subscription;
     private readonly structureLoader: StructureLoaderInterface<[ViewerCallbackManagerInterface & ViewerActionManagerInterface <R>,{entryId:string;entityId:string;}]>;
+    private readonly componentList: string[] = [];
+
+    private readonly CREATE_COMPONENT_THR: number = 5;
 
     constructor(
         structureViewer: ViewerCallbackManagerInterface & ViewerActionManagerInterface<R>,
@@ -54,21 +66,71 @@ class UniprotBehaviour<R> implements StructureViewerBehaviourInterface {
     }
 
     private subscribe(): Subscription {
-        return this.stateManager.subscribe<"model-change" | "representation-change",{pdb:{entryId:string;entityId:string;}} & {tag:"polymer"|"non-polymer";isHidden:boolean;}>(async o=>{
+        return this.stateManager.subscribe<"model-change"|"representation-change"|"feature-click",{pdb:{entryId:string;entityId:string;}} & {tag:"polymer"|"non-polymer";isHidden:boolean;} & SelectedRegion[]>(async o=>{
             if(o.type == "model-change" && o.view == "1d-view" && o.data)
                 await this.modelChange(o.data);
             if(o.type == "representation-change" && o.view == "1d-view" && o.data)
                 this.reprChange(o.data);
+            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" && o.data)
+                await this.featureClick(o.data)
+            if(o.type == "selection-change" && o.view == "3d-view")
+                await this.isSelectionEmpty();
         });
     }
 
-    featureClick(): void {
+    async featureClick(data?: SelectedRegion[]): Promise<void> {
+        let onetimeCall = (d: SelectedRegion) => {
+            const {modelId, labelAsymId, region, operatorName} = d;
+            const regions = [region];
+            const residues: number[] = regions.map(r=> r.begin == r.end ? [r.begin] : [r.begin,r.end]).flat().filter(r=>r!=null);
+            this.structureViewer.cameraFocus(modelId, labelAsymId, residues, operatorName);
+            onetimeCall = ()=>{};
+        };
+        await this.removeComponent();
+        if(!data || data.length == 0)
+            this.resetPluginView();
+        data?.forEach(d=>{
+            const {modelId, labelAsymId, region, operatorName} = d;
+            const regions = [region];
+            if(modelId && labelAsymId && 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;
+                if(residues.length == 1)
+                    this.structureViewer.setFocus(modelId,labelAsymId,residues[0],residues[0],operatorName);
+                onetimeCall(d);
+                const ranges: SaguaroRange[] = regions.map(r=>({
+                    modelId,
+                    labelAsymId,
+                    begin: r.begin,
+                    end: r.end
+                }));
+                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];
+                        const selectedComponentId = `${modelId}${TagDelimiter.instance}${labelAsymId +":"+ ((x === y) ? x.toString() : x.toString()+","+y.toString())}`;
+                        await this.structureViewer.createComponent(selectedComponentId!,ranges, "ball-and-stick");
+                        this.componentList.push(selectedComponentId);
+                    });
+
+            }else{
+                this.structureViewer.clearSelection("select", {modelId, labelAsymId});
+            }
+        })
     }
 
     hoverChange(): void {
+        this.select("hover")
     }
 
     selectionChange(): void {
+        this.select("select")
     }
 
     unsubscribe(): void {
@@ -78,14 +140,20 @@ class UniprotBehaviour<R> implements StructureViewerBehaviourInterface {
         if(data){
             switch (data.tag){
                 case "aligned":
-                    const asymId: string|undefined = this.stateManager.assemblyModelSate.getModelChainInfo(`${data.pdb.entryId}${TagDelimiter.entity}${data.pdb.entityId}`)?.chains[0].label;
-                    const componentId: string = `${data.pdb.entryId}${TagDelimiter.entity}${data.pdb.entityId}${TagDelimiter.instance}${asymId}${TagDelimiter.assembly}${"polymer"}`;
-                    this.structureViewer.displayComponent(componentId, !data.isHidden);
+                    const chain: ChainInfo|undefined = this.stateManager.assemblyModelSate.getModelChainInfo(`${data.pdb.entryId}${TagDelimiter.entity}${data.pdb.entityId}`)?.chains.find(ch=>ch.entityId==data.pdb.entityId);
+                    if(chain){
+                        const asymId: string|undefined = chain.label;
+                        const operatorInfo: OperatorInfo[] = chain.operators ?? [];
+                        const componentId: string = `${data.pdb.entryId}${TagDelimiter.entity}${data.pdb.entityId}${TagDelimiter.instance}${asymId}${TagDelimiter.assembly}${operatorInfo[0].ids.join(",")}${TagDelimiter.assembly}${"polymer"}`;
+                        this.structureViewer.displayComponent(componentId, !data.isHidden);
+                    }
                     break;
                 case "polymer":
                     this.stateManager.assemblyModelSate.getModelChainInfo(`${data.pdb.entryId}${TagDelimiter.entity}${data.pdb.entityId}`)?.chains.map(ch=>ch.label).forEach(asymId=>{
-                        const componentId: string = `${data.pdb.entryId}${TagDelimiter.entity}${data.pdb.entityId}${TagDelimiter.instance}${asymId}${TagDelimiter.assembly}${data.tag}`;
-                        this.structureViewer.displayComponent(componentId, !data.isHidden);
+                        this.stateManager.assemblyModelSate.getModelChainInfo(`${data.pdb.entryId}${TagDelimiter.entity}${data.pdb.entityId}`)?.chains[0].operators.forEach(operatorInfo=>{
+                            const componentId: string = `${data.pdb.entryId}${TagDelimiter.entity}${data.pdb.entityId}${TagDelimiter.instance}${asymId}${TagDelimiter.assembly}${operatorInfo.ids.join(",")}${TagDelimiter.assembly}${data.tag}`;
+                            this.structureViewer.displayComponent(componentId, !data.isHidden);
+                        });
                     });
                     break;
                 case "non-polymer":
@@ -103,6 +171,38 @@ class UniprotBehaviour<R> implements StructureViewerBehaviourInterface {
             await this.structureLoader.load(this.structureViewer, data.pdb);
     }
 
+    private select(mode:"select"|"hover"): void{
+        if(this.stateManager.selectionState.getSelection(mode).length == 0)
+            this.structureViewer.clearSelection(mode);
+        this.structureViewer.select(this.stateManager.selectionState.getSelection(mode).map(selectedRegion=>{
+            return selectedRegion.regions.map(region=>{
+                return {
+                    modelId: selectedRegion.modelId,
+                    labelAsymId: selectedRegion.labelAsymId,
+                    operatorName: selectedRegion.operatorName,
+                    begin: region.begin,
+                    end: region.end
+                };
+            })
+        }).flat(), mode, "set");
+    }
+
+    private resetPluginView(): void {
+        this.structureViewer.clearFocus();
+        this.structureViewer.resetCamera();
+    }
 
+    private async isSelectionEmpty(): Promise<void> {
+        if(this.stateManager.selectionState.getLastSelection() == null) {
+            await this.removeComponent();
+            this.resetPluginView();
+        }
+    }
+
+    private async removeComponent(): Promise<void> {
+        await Promise.all(this.componentList.map(async compId=>{
+            await this.structureViewer.removeComponent(compId);
+        }));
+    }
 
 }

+ 2 - 0
src/RcsbFvStructure/StructureViewers/MolstarViewer/MolstarActionManager.ts

@@ -209,6 +209,7 @@ export class MolstarActionManager implements ViewerActionManagerInterface<LoadMo
     public async createComponent(componentLabel: string, residues: Array<SaguaroPosition>, representationType: StructureRepresentationRegistry.BuiltIn): Promise<void>;
     public async createComponent(componentLabel: string, residues: Array<SaguaroRange>, representationType: StructureRepresentationRegistry.BuiltIn): Promise<void>;
     public async createComponent(...args: any[]): Promise<void> {
+        this.innerReprChangeFlag.set(true);
         await this.removeComponent(args[0]);
         if(Array.isArray(args[1])){
             if( args[1].length > 0 ) {
@@ -224,6 +225,7 @@ export class MolstarActionManager implements ViewerActionManagerInterface<LoadMo
             await this.viewer.createComponent(args[0], {modelId: this.modelMapManager.getModelId(args[1]), labelAsymId:args[2], operatorName: args[4]}, args[3]);
         }
         this.componentMap.set(args[0], this.viewer.plugin.managers.structure.hierarchy.currentComponentGroups[this.viewer.plugin.managers.structure.hierarchy.currentComponentGroups.length-1][0]);
+        this.innerReprChangeFlag.set(false);
     }
 
     public isComponent(componentLabel: string): boolean{

+ 9 - 5
src/RcsbFvStructure/StructureViewers/MolstarViewer/TrajectoryPresetProvider/AlignmentRepresentationPresetProvider.ts

@@ -52,20 +52,24 @@ export const AlignmentRepresentationPresetProvider = StructureRepresentationPres
             for(const unit of structure.units) {
                 StructureElement.Location.set(l, structure, unit, unit.elements[0]);
                 const asymId = SP.chain.label_asym_id(l);
-                if(asymObserved[asymId])
-                    continue;
-                asymObserved[asymId] = true;
                 const operators = SP.unit.pdbx_struct_oper_list_ids(l);
+                const operatorName = SP.unit.operator_name(l);
+                if(asymObserved[`${asymId}${TagDelimiter.assembly}${operatorName}`])
+                    continue;
+                asymObserved[`${asymId}${TagDelimiter.assembly}${operatorName}`] = true;
                 const type = SP.entity.type(l);
                 if (type == "polymer") {
                     const comp = await plugin.builders.structure.tryCreateComponentFromExpression(
                         structureCell,
                         MS.struct.generator.atomGroups({
-                            'chain-test': MS.core.rel.eq([MS.ammp('label_asym_id'), asymId])
+                            'chain-test': MS.core.logic.and([
+                                MS.core.rel.eq([MS.ammp('label_asym_id'), asymId]),
+                                MS.core.rel.eq([MS.acp('operatorName'), operatorName])
+                            ])
                         }),
                         uniqid(`${entryId}${TagDelimiter.entity}${entityId}${TagDelimiter.instance}${asymId}${TagDelimiter.entity}${operators.join(",")}`),
                         {
-                            label: `${entryId}${TagDelimiter.entity}${entityId}${TagDelimiter.instance}${asymId}${TagDelimiter.assembly}${type}`
+                            label: `${entryId}${TagDelimiter.entity}${entityId}${TagDelimiter.instance}${asymId}${TagDelimiter.assembly}${operators.join(",")}${TagDelimiter.assembly}${type}`
                         }
                     );
                     //TODO This needs to be called after tryCreateComponentFromExpression