Bläddra i källkod

Hover highlight callback in assembly view

bioinsilico 4 år sedan
förälder
incheckning
08103dddaa

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 329 - 378
package-lock.json


+ 5 - 4
package.json

@@ -13,6 +13,7 @@
     "tscExamples": "tsc --project ./tsconfig.examples.json",
     "build": "webpack --config ./webpack.config.js",
     "buildApp": "npm run cleanAll && npm run tsc && npm run cpStyles && npm run copyConfig && npm run build && npm run tscExamples && npm run copyHtml && npm run buildExamples && npm run clean",
+    "buildOnlyApp": "npm run cleanAll && npm run tsc && npm run cpStyles && npm run copyConfig && npm run build && npm run clean",
     "buildExamples": "webpack --config ./webpack.examples.config.js",
     "buildOnlyExamples": "npm run cleanAll && npm run tscExamples && npm run cpStyles && npm run copyHtml && npm run buildExamples && npm run clean",
     "cpStyles": "ncp src/styles build/src/styles",
@@ -50,7 +51,6 @@
     "@babel/core": "^7.10.4",
     "@babel/plugin-proposal-class-properties": "^7.10.4",
     "@babel/preset-env": "^7.10.4",
-    "@types/react": "^16.9.49",
     "@types/react-dom": "^16.9.8",
     "babel-loader": "^8.1.0",
     "concurrently": "^5.3.0",
@@ -74,9 +74,10 @@
     "webpack-cli": "^3.3.12"
   },
   "dependencies": {
-    "@rcsb/rcsb-molstar": "^1.0.26",
-    "@rcsb/rcsb-saguaro": "^1.0.2",
-    "@rcsb/rcsb-saguaro-app": "^1.0.10",
+    "@rcsb/rcsb-molstar": "^1.0.29",
+    "@rcsb/rcsb-saguaro": "^1.0.5",
+    "@rcsb/rcsb-saguaro-app": "^1.0.14",
+    "@types/react": "^16.9.49",
     "molstar": "^1.2.3"
   },
   "bugs": {

+ 54 - 21
src/RcsbFvSelection/RcsbFvSelection.ts

@@ -14,23 +14,34 @@ export interface ChainSelectionInterface {
 export class RcsbFvSelection {
 
     private selection: Array<ChainSelectionInterface> = new Array<ChainSelectionInterface>();
+    private hover: Array<ChainSelectionInterface> = new Array<ChainSelectionInterface>();
+
+    public setSelectionFromRegion(modelId: string, labelAsymId: string, region: {begin:number, end:number}, mode:'select'|'hover'): void {
+        if(mode === 'select'){
+            this.selection = new Array<ChainSelectionInterface>();
+            this.selection.push({modelId:modelId, labelAsymId:labelAsymId, regions:[region]});
+        }else{
+            this.hover = new Array<ChainSelectionInterface>();
+            this.hover.push({modelId:modelId, labelAsymId:labelAsymId, regions:[region]});
+        }
 
-    public setSelectionFromRegion(modelId: string, labelAsymId: string, region: {begin:number, end:number}): void {
-        this.selection = new Array<ChainSelectionInterface>();
-        this.selection.push({modelId:modelId, labelAsymId:labelAsymId, regions:[region]});
     }
 
-    public addSelectionFromRegion(modelId: string, labelAsymId: string, region: {begin:number, end:number}): void {
-        this.selection.push({modelId:modelId, labelAsymId:labelAsymId, regions:[region]});
+    public addSelectionFromRegion(modelId: string, labelAsymId: string, region: {begin:number, end:number}, mode:'select'|'hover'): void {
+        if(mode === 'select'){
+            this.selection.push({modelId:modelId, labelAsymId:labelAsymId, regions:[region]});
+        }else{
+            this.hover.push({modelId:modelId, labelAsymId:labelAsymId, regions:[region]});
+        }
     }
 
-    public setSelectionFromMultipleRegions(regions: {modelId: string, labelAsymId: string, region: {begin:number, end:number}}[]): void {
+    public setSelectionFromMultipleRegions(regions: {modelId: string, labelAsymId: string, region: {begin:number, end:number}}[], mode:'select'|'hover'): void {
         regions.forEach(r=>{
-            this.addSelectionFromRegion(r.modelId, r.labelAsymId, r.region);
+            this.addSelectionFromRegion(r.modelId, r.labelAsymId, r.region, mode);
         });
     }
 
-    public setSelectionFromResidueSelection(res: Array<ResidueSelectionInterface>): void {
+    public setSelectionFromResidueSelection(res: Array<ResidueSelectionInterface>, mode:'select'|'hover'): void {
         const selMap: Map<string,Map<string,Set<number>>> = new Map<string, Map<string, Set<number>>>();
         res.forEach(r=>{
             if(!selMap.has(r.modelId))
@@ -41,26 +52,48 @@ export class RcsbFvSelection {
                 selMap.get(r.modelId)!.get(r.labelAsymId)!.add(s);
             })
         });
-        this.selection = new Array<ChainSelectionInterface>();
-        selMap.forEach((labelMap, modelId)=>{
-            labelMap.forEach((seqSet,labelId)=>{
-                this.selection.push({modelId:modelId, labelAsymId: labelId, regions:RcsbFvSelection.buildIntervals(seqSet)});
+        if(mode==='select'){
+            this.selection = new Array<ChainSelectionInterface>();
+            selMap.forEach((labelMap, modelId)=>{
+                labelMap.forEach((seqSet,labelId)=>{
+                    this.selection.push({modelId:modelId, labelAsymId: labelId, regions:RcsbFvSelection.buildIntervals(seqSet)});
+                });
             });
-        });
+        }else{
+            this.hover = new Array<ChainSelectionInterface>();
+            selMap.forEach((labelMap, modelId)=>{
+                labelMap.forEach((seqSet,labelId)=>{
+                    this.hover.push({modelId:modelId, labelAsymId: labelId, regions:RcsbFvSelection.buildIntervals(seqSet)});
+                });
+            });
+        }
+
     }
 
-    public getSelection(): Array<ChainSelectionInterface> {
-        return this.selection;
+    public getSelection(mode:'select'|'hover'): Array<ChainSelectionInterface> {
+        if(mode === 'select')
+            return this.selection;
+        else
+            return this.hover;
     }
 
-    public getSelectionWithCondition(modelId: string, labelAsymId: string): ChainSelectionInterface | undefined{
-        const sel: Array<ChainSelectionInterface> = this.selection.filter(d=>(d.modelId===modelId && d.labelAsymId === labelAsymId));
-        if(sel.length > 0)
-            return sel[0]
+    public getSelectionWithCondition(modelId: string, labelAsymId: string, mode:'select'|'hover'): ChainSelectionInterface | undefined{
+        if(mode === 'select'){
+            const sel: Array<ChainSelectionInterface> = this.selection.filter(d=>(d.modelId===modelId && d.labelAsymId === labelAsymId));
+            if(sel.length > 0)
+                return sel[0]
+        }else{
+            const sel: Array<ChainSelectionInterface> = this.hover.filter(d=>(d.modelId===modelId && d.labelAsymId === labelAsymId));
+            if(sel.length > 0)
+                return sel[0]
+        }
     }
 
-    public clearSelection(): void {
-        this.selection = new Array<ChainSelectionInterface>();
+    public clearSelection(mode:'select'|'hover'): void {
+        if(mode === 'select')
+            this.selection = new Array<ChainSelectionInterface>();
+        else
+            this.hover = new Array<ChainSelectionInterface>();
     }
 
     private static buildIntervals(ids: Set<number>): Array<{begin:number,end:number}>{

+ 17 - 7
src/RcsbFvSequence/SequenceViews/AbstractView.tsx

@@ -5,7 +5,7 @@ import {
     SaguaroPluginInterface,
     SaguaroPluginModelMapType
 } from "../../RcsbFvStructure/StructurePlugins/SaguaroPluginInterface";
-import { RcsbFvSelection} from "../../RcsbFvSelection/RcsbFvSelection";
+import {RcsbFvSelection, ResidueSelectionInterface} from "../../RcsbFvSelection/RcsbFvSelection";
 
 export interface AbstractViewInterface {
     componentId: string;
@@ -44,14 +44,22 @@ export abstract class AbstractView<P,S> extends React.Component <P & AbstractVie
     componentDidMount() {
         this.props.plugin.setSelectCallback(this.structureSelectionCallback.bind(this));
         this.props.plugin.setModelChangeCallback(this.modelChangeCallback.bind(this));
-        window.addEventListener('resize', ()=>{
-            window.clearTimeout(this.updateDimTimeout);
-            this.updateDimTimeout = window.setTimeout(()=> {
-                this.updateDimensions();
-            },100);
-        });
+        this.props.plugin.setHoverCallback(this.structureHoverCallback.bind(this));
+        window.addEventListener('resize', this.resizeCallback);
     }
 
+    componentWillUnmount() {
+        this.props.plugin.unsetCallbacks();
+        window.removeEventListener('resize', this.resizeCallback);
+    }
+
+    protected resizeCallback: ()=>void =  () => {
+        window.clearTimeout(this.updateDimTimeout);
+        this.updateDimTimeout = window.setTimeout(()=> {
+            this.updateDimensions();
+        },100);
+    };
+
     private createTitle(): JSX.Element | null{
         if(this.props.title)
             return (<div id={RcsbFvDOMConstants.TITLE_ID} className={classes.rcsbFvTitle}>{this.props.title}</div>)
@@ -66,6 +74,8 @@ export abstract class AbstractView<P,S> extends React.Component <P & AbstractVie
 
     protected structureSelectionCallback(): void{}
 
+    protected structureHoverCallback(): void{}
+
     protected modelChangeCallback(modelMap:SaguaroPluginModelMapType): void{}
 
     protected updateDimensions(): void{}

+ 51 - 8
src/RcsbFvSequence/SequenceViews/AssemblyView.tsx

@@ -22,6 +22,7 @@ export class AssemblyView extends AbstractView<AssemblyViewInterface & AbstractV
     private currentLabelId: string;
     private currentEntryId: string;
     private currentModelId: string;
+    private createComponentThreshold: number = 9;
 
     constructor(props: AssemblyViewInterface & AbstractViewInterface) {
         super({
@@ -47,27 +48,66 @@ export class AssemblyView extends AbstractView<AssemblyViewInterface & AbstractV
         setBoardConfig({
             trackWidth: trackWidth,
             elementClickCallBack:(e: RcsbFvTrackDataElementInterface)=>{
-                if(e == null)
+                console.log(e);
+                if(e == null) {
+                    this.props.plugin.clearSelection('select');
                     return;
+                }
                 const x = e.begin;
                 const y = e.end ?? e.begin;
-                this.props.plugin.clearSelect();
-                this.props.plugin.select(this.currentModelId, this.currentLabelId,x,y);
-                this.props.selection.setSelectionFromRegion(this.currentModelId, this.currentLabelId, {begin:x, end:y});
+                this.props.plugin.clearSelection('select');
+                if(e.isEmpty){
+                    this.props.plugin.selectSet(
+                        [{modelId: this.currentModelId, asymId: this.currentLabelId, position: x},{modelId: this.currentModelId, asymId: this.currentLabelId, position: y}], 'select'
+                    );
+                    this.props.selection.setSelectionFromMultipleRegions(
+                        [
+                            {modelId: this.currentModelId, labelAsymId: this.currentLabelId, region: {begin:x, end: x}},
+                            {modelId: this.currentModelId, labelAsymId: this.currentLabelId, region: {begin: y, end: y}}
+                            ], 'select'
+                    );
+                    this.props.plugin.removeComponent();
+                    this.props.plugin.createComponentFromSet(this.currentModelId,[{asymId:this.currentLabelId, position:x}, {asymId:this.currentLabelId, position:y}], 'spacefill');
+                }else{
+                    this.props.plugin.selectRange(this.currentModelId, this.currentLabelId,x,y, 'select');
+                    this.props.selection.setSelectionFromRegion(this.currentModelId, this.currentLabelId, {begin:x, end:y}, 'select');
+                    this.props.plugin.removeComponent();
+                    if((y-x)<this.createComponentThreshold){
+                        this.props.plugin.createComponentFromRange(this.currentModelId, this.currentLabelId, x, y, 'spacefill');
+                    }
+                }
+            },
+            highlightHoverPosition:true,
+            highlightHoverCallback:(selection: RcsbFvTrackDataElementInterface[])=>{
+                this.props.plugin.clearSelection('hover');
+                if(selection != null && selection.length > 0)
+                    this.props.plugin.selectRange(this.currentModelId, this.currentLabelId,selection[0].begin,selection[0].end ?? selection[0].begin,'hover');
             }
         });
     }
 
     componentWillUnmount() {
+        super.componentWillUnmount();
         unmount(this.pfvDivId);
     }
 
     protected structureSelectionCallback(): void{
-        const sel: ChainSelectionInterface | undefined = this.props.selection.getSelectionWithCondition(this.currentModelId, this.currentLabelId)
+       this.pluginSelectCallback('select');
+    }
+
+    protected structureHoverCallback(): void{
+        this.pluginSelectCallback('hover');
+    }
+
+    private pluginSelectCallback(mode:'select'|'hover'): void{
+        const sel: ChainSelectionInterface | undefined = this.props.selection.getSelectionWithCondition(this.currentModelId, this.currentLabelId, mode)
+
+        if(getRcsbFv(this.pfvDivId) == null)
+            return;
         if(sel == null)
-            getRcsbFv(this.pfvDivId).clearSelection();
+            getRcsbFv(this.pfvDivId).clearSelection(mode);
         else
-            getRcsbFv(this.pfvDivId).setSelection(sel.regions);
+            getRcsbFv(this.pfvDivId).setSelection({elements:sel.regions, mode:mode});
     }
 
     protected modelChangeCallback(modelMap:SaguaroPluginModelMapType): void {
@@ -93,7 +133,10 @@ export class AssemblyView extends AbstractView<AssemblyViewInterface & AbstractV
             undefined,
             onChangeCallback.get(entryId),
             filterInstances.get(entryId)
-        );
+        ).then(()=>{
+            const length: number = getRcsbFv(this.pfvDivId).getBoardConfig().length ?? 0;
+            this.createComponentThreshold = (((Math.floor(length/100))+1)*10)-1;
+        });
     }
 
     protected updateDimensions(): void{

+ 15 - 13
src/RcsbFvSequence/SequenceViews/CustomView.tsx

@@ -15,7 +15,7 @@ import {
 export type CustomViewStateInterface = Omit<CustomViewInterface, "modelChangeCallback">;
 
 export interface CustomViewInterface {
-    config: FeatureBlockInterface | Array<FeatureBlockInterface>;
+    blockConfig: FeatureBlockInterface | Array<FeatureBlockInterface>;
     additionalContent?: (select: BlockViewSelector) => JSX.Element;
     modelChangeCallback?: (modelMap: SaguaroPluginModelMapType) => (void | CustomViewStateInterface);
 }
@@ -24,7 +24,7 @@ export interface FeatureBlockInterface {
     blockId:string;
     blockTitle?: string;
     blockShortName?: string;
-    blockConfig: Array<FeatureViewInterface> | FeatureViewInterface;
+    featureViewConfig: Array<FeatureViewInterface> | FeatureViewInterface;
 }
 
 export interface FeatureViewInterface {
@@ -64,7 +64,7 @@ export class CustomView extends AbstractView<CustomViewInterface & AbstractViewI
     private firstModelLoad: boolean = true;
 
     readonly state: CustomViewStateInterface = {
-        config: this.props.config,
+        blockConfig: this.props.blockConfig,
         additionalContent: this.props.additionalContent
     };
 
@@ -72,15 +72,16 @@ export class CustomView extends AbstractView<CustomViewInterface & AbstractViewI
         super({
             ...props
         });
-       this.mapBlocks(props.config);
+       this.mapBlocks(props.blockConfig);
     }
 
     componentDidMount(): void {
         super.componentDidMount();
-        this.blockViewSelector.setActiveBlock( (this.state.config instanceof Array ? this.state.config : [this.state.config])[0].blockId! );
+        this.blockViewSelector.setActiveBlock( (this.state.blockConfig instanceof Array ? this.state.blockConfig : [this.state.blockConfig])[0].blockId! );
     }
 
     componentWillUnmount() {
+        super.componentWillUnmount();
         this.rcsbFvMap.forEach((pfv,id)=>{
             pfv.unmount();
         });
@@ -92,7 +93,7 @@ export class CustomView extends AbstractView<CustomViewInterface & AbstractViewI
         ( config instanceof Array ? config : [config]).forEach(block=>{
             if(block.blockId == null)block.blockId = "block_"+Math.random().toString(36).substr(2);
             if(!this.blockMap.has(block.blockId))this.blockMap.set(block.blockId, new Array<string>());
-            (block.blockConfig instanceof Array ? block.blockConfig : [block.blockConfig]).forEach(board=>{
+            (block.featureViewConfig instanceof Array ? block.featureViewConfig : [block.featureViewConfig]).forEach(board=>{
                 if(board.boardId == null)board.boardId = "board_"+Math.random().toString(36).substr(2);
                 this.blockMap.get(block.blockId!)?.push(board.boardId);
                 this.boardMap.set(board.boardId, board);
@@ -116,6 +117,7 @@ export class CustomView extends AbstractView<CustomViewInterface & AbstractViewI
             document.getElementById("boardDiv_"+boardId)?.remove()
         });
         this.rcsbFvMap.clear();
+        this.props.plugin.unsetCallbacks();
     }
 
     private buildBlockFv(){
@@ -169,14 +171,14 @@ export class CustomView extends AbstractView<CustomViewInterface & AbstractViewI
         if(typeof this.props.modelChangeCallback === "function") {
             const newConfig: CustomViewStateInterface | void = this.props.modelChangeCallback(modelMap);
             if(newConfig != null){
-                if(newConfig.config != null && newConfig.additionalContent != null){
-                    this.mapBlocks(newConfig.config);
-                    this.setState({config: newConfig.config, additionalContent: newConfig.additionalContent})
-                }else if(newConfig.config == null && newConfig.additionalContent != null){
+                if(newConfig.blockConfig != null && newConfig.additionalContent != null){
+                    this.mapBlocks(newConfig.blockConfig);
+                    this.setState({blockConfig: newConfig.blockConfig, additionalContent: newConfig.additionalContent})
+                }else if(newConfig.blockConfig == null && newConfig.additionalContent != null){
                     this.setState({additionalContent: newConfig.additionalContent})
-                }else if(newConfig.config != null && newConfig.additionalContent == null){
-                    this.mapBlocks(newConfig.config);
-                    this.setState({config: newConfig.config})
+                }else if(newConfig.blockConfig != null && newConfig.additionalContent == null){
+                    this.mapBlocks(newConfig.blockConfig);
+                    this.setState({blockConfig: newConfig.blockConfig})
                 }
             }
         }

+ 76 - 14
src/RcsbFvStructure/StructurePlugins/MolstarPlugin.ts

@@ -18,6 +18,9 @@ import {State, StateSelection} from "molstar/lib/mol-state";
 import {StructureRef} from "molstar/lib/mol-plugin-state/manager/structure/hierarchy-state";
 import {RcsbFvSelection, ResidueSelectionInterface} from "../../RcsbFvSelection/RcsbFvSelection";
 import {AbstractPlugin} from "./AbstractPlugin";
+import {Subscription} from "rxjs";
+import {InteractivityManager} from "molstar/lib/mol-plugin-state/manager/interactivity";
+import {StateBuilder} from "molstar/lib/mol-state/state/builder";
 
 export enum LoadMethod {
     loadPdbId = "loadPdbId",
@@ -46,10 +49,12 @@ interface LoadParams {
 
 export class MolstarPlugin extends AbstractPlugin implements SaguaroPluginInterface, SaguaroPluginPublicInterface {
     private plugin: Viewer;
-    private innerSelectionFlag: boolean = false;
+    private innerSelectionFlag: number = 0;
     private loadingFlag: boolean = false;
     private modelChangeCallback: (chainMap:SaguaroPluginModelMapType)=>void;
     private modelMap: Map<string,string|undefined> = new Map<string, string>();
+    private selectCallbackSubs: Subscription;
+    private modelChangeCallbackSubs: Subscription;
 
     constructor(props: RcsbFvSelection) {
         super(props);
@@ -119,15 +124,64 @@ export class MolstarPlugin extends AbstractPlugin implements SaguaroPluginInterf
     public setBackground(color: number) {
     }
 
-    public select(modelId:string, asymId: string, x: number, y: number): void {
-        this.innerSelectionFlag = true;
-        this.plugin.select(this.getModelId(modelId), asymId, x, y)
+    public selectRange(modelId:string, asymId: string, begin: number, end: number, mode: 'select'|'hover'): void {
+        if(mode == null || mode === 'select') {
+            this.innerSelectionFlag += 1;
+        }
+        this.plugin.select(this.getModelId(modelId), asymId, begin, end, mode);
+    }
+    public selectSet(selection: Array<{modelId:string; asymId: string; position: number;}>, mode: 'select'|'hover'): void {
+        if(mode == null || mode === 'select') {
+            this.innerSelectionFlag += 1;
+        }
+        this.plugin.select(selection.map(r=>{return{modelId: this.getModelId(r.modelId), position:r.position, asymId: r.asymId}}), mode);
+    }
+
+    public createComponentFromRange(modelId:string, asymId: string, begin: number, end : number, representationType: 'ball-and-stick' | 'spacefill' | 'gaussian-surface' | 'cartoon'): void {
+        this.plugin.createComponentFromRange("1D annotation", this.getModelId(modelId), asymId, begin, end, representationType);
+    }
+
+    public createComponentFromSet(modelId:string, residues: Array<{asymId: string; position: number;}>, representationType: 'ball-and-stick' | 'spacefill' | 'gaussian-surface' | 'cartoon'): void {
+        this.plugin.createComponentFromSet("1D annotation", this.getModelId(modelId), residues, representationType);
+    }
+
+    public removeComponent(): void{
+        this.plugin.removeComponent("1D annotation");
     }
 
-    public setSelectCallback(g:()=>void ){
-        this.plugin.getPlugin().managers.structure.selection.events.changed.subscribe(()=>{
-            if(this.innerSelectionFlag) {
-                this.innerSelectionFlag = false;
+    public setHoverCallback(g:()=>void){
+        this.plugin.getPlugin().managers.structure.component.events.optionsUpdated.subscribe(()=>{
+            console.log("!!!!!!!");
+        });
+        this.plugin.getPlugin().behaviors.interaction.hover.subscribe((r: InteractivityManager.HoverEvent)=>{
+            const sequenceData: Array<ResidueSelectionInterface> = new Array<ResidueSelectionInterface>();
+            const loci: Loci = r.current.loci;
+            if(StructureElement.Loci.is(loci)){
+                const loc = StructureElement.Location.create(loci.structure);
+                for (const e of loci.elements) {
+                    const modelId: string = e.unit?.model?.id;
+                    const seqIds = new Set<number>();
+                    loc.unit = e.unit;
+                    for (let i = 0, il = OrderedSet.size(e.indices); i < il; ++i) {
+                        loc.element = e.unit.elements[OrderedSet.getAt(e.indices, i)];
+                        seqIds.add(SP.residue.label_seq_id(loc));
+                    }
+                    sequenceData.push({
+                        modelId: this.getModelId(modelId),
+                        labelAsymId: SP.chain.label_asym_id(loc),
+                        seqIds
+                    });
+                }
+            }
+            this.selection.setSelectionFromResidueSelection(sequenceData, 'hover');
+            g();
+        });
+    }
+
+    public setSelectCallback(g:()=>void){
+        this.selectCallbackSubs = this.plugin.getPlugin().managers.structure.selection.events.changed.subscribe(()=>{
+            if(this.innerSelectionFlag > 0) {
+                this.innerSelectionFlag -= 1;
                 return;
             }
             const sequenceData: Array<ResidueSelectionInterface> = new Array<ResidueSelectionInterface>();
@@ -153,15 +207,17 @@ export class MolstarPlugin extends AbstractPlugin implements SaguaroPluginInterf
 
                 }
             }
-            this.selection.setSelectionFromResidueSelection(sequenceData);
+            this.selection.setSelectionFromResidueSelection(sequenceData, 'select');
             g();
         });
     }
 
-    public clearSelect(): void {
-        this.innerSelectionFlag = true;
-        this.plugin.getPlugin().managers.interactivity.lociSelects.deselectAll();
-        this.selection.clearSelection();
+    public clearSelection(mode:'select'|'hover'): void {
+        if(mode === 'select') {
+            this.innerSelectionFlag += 1;
+        }
+        this.plugin.clearSelection(mode);
+
     }
 
     public pluginCall(f: (plugin: PluginContext) => void){
@@ -170,7 +226,7 @@ export class MolstarPlugin extends AbstractPlugin implements SaguaroPluginInterf
 
     public setModelChangeCallback(f:(modelMap:SaguaroPluginModelMapType)=>void){
         this.modelChangeCallback = f;
-        this.plugin.getPlugin().state.events.object.updated.subscribe((o)=>{
+        this. modelChangeCallbackSubs = this.plugin.getPlugin().state.events.object.updated.subscribe((o)=>{
             if(this.loadingFlag)
                 return;
             if(o.action === "in-place" && o.ref === "ms-plugin.create-structure-focus-representation") {
@@ -208,6 +264,12 @@ export class MolstarPlugin extends AbstractPlugin implements SaguaroPluginInterf
         return this.modelMap.get(id) ?? id;
     }
 
+    public unsetCallbacks(): void {
+        this.selectCallbackSubs?.unsubscribe();
+        this.modelChangeCallbackSubs?.unsubscribe();
+    }
+
+
 }
 
 function getChainValues(structure: Structure, modelEntityId: string): [{modelId:string, entryId:string},{auth:string;label:string}[]] {

+ 15 - 4
src/RcsbFvStructure/StructurePlugins/SaguaroPluginInterface.ts

@@ -6,14 +6,25 @@ export type SaguaroPluginModelMapType = Map<string,{entryId: string; chains:Arra
 export interface SaguaroPluginInterface {
     init: (elementId: string, props?: any) => void;
     load: (args: LoadMolstarInterface) => void;
-    select: (modelId: string, asymId: string, x: number, y: number) => void;
-    clearSelect: () => void;
     pluginCall: (f:(plugin: PluginContext)=>void) => void;
-    setSelectCallback: (g:()=>void)=>void;
     clear: () => void;
+    setSelectCallback: (g:()=>void)=>void;
     setModelChangeCallback: (f:(modelMap:SaguaroPluginModelMapType)=>void)=>void;
+    setHoverCallback:(g:()=>void)=>void;
+    unsetCallbacks:()=>void;
+    selectRange: (modelId:string, asymId: string, x: number, y: number, mode: 'select'|'hover') => void;
+    selectSet: (selection: Array<{modelId:string; asymId: string; position: number;}>, mode: 'select'|'hover') => void;
+    clearSelection: (mode:'select'|'hover') => void;
+    createComponentFromRange: (modelId:string, asymId: string, x: number, y: number, representationType: 'ball-and-stick' | 'spacefill' | 'gaussian-surface' | 'cartoon') => void;
+    createComponentFromSet: (modelId:string, residues: Array<{asymId: string; position: number;}>, representationType: 'ball-and-stick' | 'spacefill' | 'gaussian-surface' | 'cartoon') => void;
+    removeComponent: () => void;
 }
 
 export interface SaguaroPluginPublicInterface {
-    select: (modelId:string, asymId: string, x: number, y: number) => void;
+    selectRange: (modelId:string, asymId: string, x: number, y: number, mode: 'select'|'hover') => void;
+    selectSet: (selection: Array<{modelId:string; asymId: string; position: number;}>, mode: 'select'|'hover') => void;
+    clearSelection: (mode:'select'|'hover') => void;
+    createComponentFromRange: (modelId:string, asymId: string, x: number, y: number, representationType: 'ball-and-stick' | 'spacefill' | 'gaussian-surface' | 'cartoon') => void;
+    createComponentFromSet: (modelId:string, residues: Array<{asymId: string; position: number;}>, representationType: 'ball-and-stick' | 'spacefill' | 'gaussian-surface' | 'cartoon') => void;
+    removeComponent: () => void;
 }

+ 0 - 5
src/examples/assembly/example.ts

@@ -1,10 +1,5 @@
 
-import {RcsbFv3DCustomBuilder} from "../../RcsbFv3D/RcsbFv3DCustom";
-import {StructureViewInterface} from "../../RcsbFvStructure/RcsbFvStructure";
-import {SequenceViewInterface} from "../../RcsbFvSequence/RcsbFvSequence";
-
 import './example.html';
-import {LoadMethod} from "../../RcsbFvStructure/StructurePlugins/MolstarPlugin";
 import {RcsbFv3DAssembly} from "../../RcsbFv3D/RcsbFv3DAssembly";
 
 document.addEventListener("DOMContentLoaded", function(event) {

+ 5 - 5
src/examples/custom-panel/example.tsx

@@ -117,23 +117,23 @@ const fv2: FeatureViewInterface = {
 
 const block: FeatureBlockInterface = {
     blockId:"MyBlock_1",
-    blockConfig: [fv1]
+    featureViewConfig: [fv1]
 };
 
 const block2: FeatureBlockInterface = {
     blockId:"MyBlock_2",
-    blockConfig: [fv2, fv1]
+    featureViewConfig: [fv2, fv1]
 };
 
 const block3: FeatureBlockInterface = {
     blockId:"MyBlock_3",
-    blockConfig: [fv1, fv2]
+    featureViewConfig: [fv1, fv2]
 };
 
 const modelChangeCallback = (modelMap: SaguaroPluginModelMapType) => {
     console.log(modelMap);
     return {
-      config:[block, block2, block3],
+      blockConfig:[block, block2, block3],
       additionalContent:(select: BlockViewSelector) => {
           function changeBlock(select: BlockViewSelector){
               select.setActiveBlock("MyBlock_2");
@@ -162,7 +162,7 @@ const modelChangeCallback = (modelMap: SaguaroPluginModelMapType) => {
 };
 
 const customConfig: CustomViewInterface = {
-    config:[block, block2],
+    blockConfig:[block, block2],
     additionalContent:additionalContent,
     modelChangeCallback:modelChangeCallback
 }

+ 72 - 0
src/examples/multiple-entries/MultipleEntries.tsx

@@ -0,0 +1,72 @@
+import * as React from "react";
+import {RcsbFv3DCustomInterface} from "../../RcsbFv3D/RcsbFv3DCustom";
+import {LoadMethod} from "../../RcsbFvStructure/StructurePlugins/MolstarPlugin";
+import {SaguaroPluginModelMapType} from "../../RcsbFvStructure/StructurePlugins/SaguaroPluginInterface";
+import {buildInstanceSequenceFv} from "@rcsb/rcsb-saguaro-app";
+import {RcsbFvDOMConstants} from "../../RcsbFvConstants/RcsbFvConstants";
+
+interface MultipleEntriesInterface {
+    config: {
+            pdbId: string;
+            id:string;
+    }[]
+}
+
+export class MultipleEntries {
+
+    constructor() {
+        const config: RcsbFv3DCustomInterface = {
+            elementId:"test",
+            structurePanelConfig:{
+                loadConfig: {
+                    method: LoadMethod.loadPdbIds,
+                    params: []
+                }
+            },
+            sequencePanelConfig:{
+                config:{
+                    blockConfig:[],
+                    modelChangeCallback:(chainMap: SaguaroPluginModelMapType)=>{
+                        return {
+                            blockConfig: {
+                                blockId:"uniqueBlock",
+                                featureViewConfig:[]
+                            },
+                            additionalContent: (select)=>{
+                                return (
+                                    <div>
+                                        <select onChange={
+                                            (evt)=>{
+                                                this.selectPdbChangeCalback(chainMap.get(evt.target.value))
+                                            }
+                                        }>
+                                            {
+                                                Array.from(chainMap.keys()).map(pdbId=>{
+                                                    return <option value={pdbId} >{pdbId}</option>
+                                                })
+                                            }
+                                        </select>
+                                        <div id={"chainSelectDiv"} />
+                                    </div>
+                                );
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    private selectPdbChangeCalback(modelData: {entryId: string; chains:Array<{label:string, auth:string}>} | undefined){
+
+        /*buildInstanceSequenceFv(
+            this.pfvDivId,
+            RcsbFvDOMConstants.SELECT_INSTANCE_PFV_ID,
+            entryId,
+            undefined,
+            onChangeCallback.get(entryId),
+            filterInstances.get(entryId)
+        );*/
+    }
+
+}

Vissa filer visades inte eftersom för många filer har ändrats