AssemblyView.tsx 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356
  1. import {RcsbFvDOMConstants} from "../../../RcsbFvConstants/RcsbFvConstants";
  2. import * as React from "react";
  3. import {
  4. buildInstanceSequenceFv,
  5. buildMultipleInstanceSequenceFv,
  6. getRcsbFv,
  7. setBoardConfig,
  8. unmount
  9. } from "@rcsb/rcsb-saguaro-app";
  10. import {AbstractView, AbstractViewInterface} from "../AbstractView";
  11. import {InstanceSequenceOnchangeInterface} from "@rcsb/rcsb-saguaro-app/build/dist/RcsbFvWeb/RcsbFvBuilder/RcsbFvInstanceBuilder";
  12. import {RcsbFvTrackDataElementInterface} from "@rcsb/rcsb-saguaro";
  13. import {ChainSelectionInterface} from "../../../RcsbFvSelection/RcsbFvSelection";
  14. import {SaguaroPluginModelMapType} from "../../../RcsbFvStructure/StructurePlugins/SaguaroPluginInterface";
  15. import {SelectionInterface} from "@rcsb/rcsb-saguaro/build/RcsbBoard/RcsbSelection";
  16. import {OptionPropsInterface} from "@rcsb/rcsb-saguaro-app/build/dist/RcsbFvWeb/WebTools/SelectButton";
  17. import {OptionProps} from "react-select/src/components/Option";
  18. import {components} from 'react-select';
  19. import {ChainDisplay} from "./ChainDisplay";
  20. import {
  21. StructureSelectionQueries as Q,
  22. StructureSelectionQuery
  23. } from 'molstar/lib/mol-plugin-state/helpers/structure-selection-query';
  24. import {StructureRepresentationRegistry} from "molstar/lib/mol-repr/structure/registry";
  25. import Expression from "molstar/lib/mol-script/language/expression";
  26. export interface AssemblyViewInterface {
  27. entryId: string;
  28. }
  29. export class AssemblyView extends AbstractView<AssemblyViewInterface & AbstractViewInterface, AssemblyViewInterface & AbstractViewInterface>{
  30. private currentLabelAsymId: string;
  31. private currentEntryId: string;
  32. private currentModelId: string;
  33. private currentModelNumber: string;
  34. private createComponentThresholdBatch = 3;
  35. private createComponentThreshold: number = 9;
  36. private innerSelectionFlag: boolean = false;
  37. private currentSelectedComponentId: string;
  38. private currentModelMap:SaguaroPluginModelMapType;
  39. //private readonly componentSet = new Map<string, {current: Set<string>, previous: Set<string>}>();
  40. constructor(props: AssemblyViewInterface & AbstractViewInterface) {
  41. super({
  42. ...props
  43. });
  44. }
  45. protected additionalContent(): JSX.Element {
  46. return (
  47. <div style={{marginTop:10}}>
  48. <div id={RcsbFvDOMConstants.SELECT_INSTANCE_PFV_ID} style={{display:"inline-block"}}/>
  49. <div style={{position:"absolute", top:5, right:5}} >
  50. <a style={{textDecoration:"none", color:"#337ab7", cursor:"pointer", marginRight:15}} target={"_blank"} href={"/docs/sequence-viewers/3d-protein-feature-view"}>
  51. Help
  52. </a>
  53. <a style={{textDecoration:"none", color: "#337ab7", cursor:"pointer"}} onClick={()=>{this.props.unmount(true)}}>
  54. Back
  55. </a>
  56. </div>
  57. </div>
  58. );
  59. }
  60. componentDidMount (): void {
  61. super.componentDidMount();
  62. const width: number | undefined = document.getElementById(this.componentDivId)?.getBoundingClientRect().width;
  63. if(width == null)
  64. return;
  65. const trackWidth: number = width - 190 - 55;
  66. setBoardConfig({
  67. trackWidth: trackWidth,
  68. elementClickCallBack:(e:RcsbFvTrackDataElementInterface)=>{
  69. this.props.plugin.clearFocus();
  70. if(this.currentSelectedComponentId != null)
  71. this.props.plugin.removeComponent(this.currentSelectedComponentId);
  72. if(e == null)
  73. return;
  74. const x = e.begin;
  75. const y = e.end ?? e.begin;
  76. if(e.isEmpty){
  77. this.props.plugin.cameraFocus(this.currentModelId, this.currentLabelAsymId, [x,y]);
  78. this.currentSelectedComponentId = this.currentLabelAsymId +":"+ ((x === y) ? x.toString() : x.toString()+","+y.toString());
  79. setTimeout(()=>{
  80. this.props.plugin.createComponent(
  81. this.currentSelectedComponentId,
  82. this.currentModelId,
  83. [{asymId: this.currentLabelAsymId, position: x}, {asymId: this.currentLabelAsymId, position: y}],
  84. 'ball-and-stick'
  85. ).then(()=>{
  86. if(x === y)
  87. setTimeout(()=>{
  88. this.props.plugin.setFocus(this.currentModelId, this.currentLabelAsymId, x, y);
  89. },200);
  90. });
  91. },100);
  92. }else{
  93. this.props.plugin.cameraFocus(this.currentModelId, this.currentLabelAsymId, x, y);
  94. if((y-x)<this.createComponentThreshold){
  95. this.currentSelectedComponentId = this.currentLabelAsymId +":"+ (x === y ? x.toString() : x.toString()+"-"+y.toString());
  96. setTimeout(()=>{
  97. this.props.plugin.createComponent(this.currentSelectedComponentId, this.currentModelId, this.currentLabelAsymId, x, y, 'ball-and-stick').then(()=>{
  98. if(x === y)
  99. setTimeout(()=>{
  100. this.props.plugin.setFocus(this.currentModelId, this.currentLabelAsymId, x, y);
  101. },200);
  102. });
  103. },100);
  104. }
  105. }
  106. },
  107. selectionChangeCallBack:(selection: Array<SelectionInterface>)=>{
  108. if(this.innerSelectionFlag)
  109. return;
  110. this.props.plugin.clearSelection('select', {modelId: this.currentModelId, labelAsymId: this.currentLabelAsymId});
  111. this.props.selection.clearSelection('select', this.currentLabelAsymId);
  112. if(selection == null || selection.length === 0) {
  113. this.resetPluginView();
  114. return;
  115. }
  116. this.select(selection);
  117. },
  118. highlightHoverPosition:true,
  119. highlightHoverElement:true,
  120. highlightHoverCallback:(selection: RcsbFvTrackDataElementInterface[])=>{
  121. this.props.plugin.clearSelection('hover');
  122. if(selection != null && selection.length > 0) {
  123. if(selection[0].isEmpty){
  124. const selectionList = [{modelId: this.currentModelId, asymId: this.currentLabelAsymId, position: selection[0].begin}];
  125. if(selection[0].end != null) selectionList.push({modelId: this.currentModelId, asymId: this.currentLabelAsymId, position: selection[0].end})
  126. this.props.plugin.select(
  127. selectionList,
  128. 'hover',
  129. 'add'
  130. );
  131. }else {
  132. this.props.plugin.select(this.currentModelId, this.currentLabelAsymId, selection[0].begin, selection[0].end ?? selection[0].begin, 'hover', 'set');
  133. }
  134. }
  135. },
  136. });
  137. }
  138. componentWillUnmount() {
  139. super.componentWillUnmount();
  140. unmount(this.pfvDivId);
  141. }
  142. protected structureSelectionCallback(): void{
  143. this.pluginSelectCallback('select');
  144. }
  145. protected structureHoverCallback(): void{
  146. this.pluginSelectCallback('hover');
  147. }
  148. protected representationChangeCallback(): void{
  149. //TODO
  150. }
  151. private pluginSelectCallback(mode:'select'|'hover'): void{
  152. if(getRcsbFv(this.pfvDivId) == null)
  153. return;
  154. this.innerSelectionFlag = true;
  155. if(mode === 'select' && this.currentSelectedComponentId != null){
  156. this.props.plugin.removeComponent(this.currentSelectedComponentId);
  157. }
  158. const allSel: Array<ChainSelectionInterface> | undefined = this.props.selection.getSelection(mode);
  159. if(allSel == null || allSel.length ===0) {
  160. getRcsbFv(this.pfvDivId).clearSelection(mode);
  161. if(mode === 'select')
  162. this.resetPluginView();
  163. }else if(mode === 'select' && this.props.selection.getLastSelection('select')?.labelAsymId != null && this.props.selection.getLastSelection('select')?.labelAsymId != this.currentLabelAsymId){
  164. const authId: string | undefined = this.currentModelMap
  165. .get(this.currentModelId)?.chains
  166. .filter(ch=>(ch.label===this.props.selection.getLastSelection('select')?.labelAsymId))[0]?.auth;
  167. this.modelChangeCallback(this.currentModelMap, authId);
  168. }else{
  169. const sel: ChainSelectionInterface | undefined = this.props.selection.getSelectionWithCondition(this.currentModelId, this.currentLabelAsymId, mode);
  170. if (sel == null) {
  171. getRcsbFv(this.pfvDivId).clearSelection(mode);
  172. if(mode === 'select')
  173. this.resetPluginView();
  174. } else {
  175. getRcsbFv(this.pfvDivId).setSelection({elements: sel.regions, mode: mode});
  176. }
  177. }
  178. this.innerSelectionFlag = false;
  179. }
  180. protected async modelChangeCallback(modelMap:SaguaroPluginModelMapType, defaultAuthId?: string): Promise<void> {
  181. this.currentModelMap = modelMap;
  182. this.props.plugin.clearFocus();
  183. const onChangeCallback: Map<string, (x: InstanceSequenceOnchangeInterface)=>void> = new Map<string, (x: InstanceSequenceOnchangeInterface) => {}>();
  184. const filterInstances: Map<string, Set<string>> = new Map<string, Set<string>>();
  185. modelMap.forEach((v,k)=>{
  186. onChangeCallback.set(v.entryId,(x)=>{
  187. this.currentEntryId = v.entryId;
  188. this.currentLabelAsymId = x.asymId;
  189. this.currentModelId = k;
  190. setTimeout(()=>{
  191. this.props.selection.setLastSelection('select', null);
  192. this.structureSelectionCallback();
  193. },1000);
  194. });
  195. filterInstances.set(v.entryId,new Set<string>(v.chains.map(d=>d.label)));
  196. });
  197. unmount(this.pfvDivId);
  198. const entryId: string = Array.from(modelMap.values()).map(d=>d.entryId)[0];
  199. if(entryId != null)
  200. buildInstanceSequenceFv(
  201. this.pfvDivId,
  202. RcsbFvDOMConstants.SELECT_INSTANCE_PFV_ID,
  203. entryId, {
  204. defaultValue: defaultAuthId,
  205. onChangeCallback: onChangeCallback.get(entryId),
  206. filterInstances: filterInstances.get(entryId),
  207. selectButtonOptionProps:(props:OptionProps<OptionPropsInterface>)=>(components.Option && <div style={{display:'flex'}}>
  208. <ChainDisplay plugin={this.props.plugin} label={props.data.label}/><components.Option {...props}/>
  209. </div>)
  210. }
  211. ).then(()=>{
  212. const length: number = getRcsbFv(this.pfvDivId).getBoardConfig().length ?? 0;
  213. this.createComponentThreshold = (((Math.floor(length/100))+1)*this.createComponentThresholdBatch)-1;
  214. });
  215. if(!defaultAuthId)
  216. await this.createComponents(modelMap);
  217. }
  218. protected updateDimensions(): void{
  219. const width: number = window.document.getElementById(this.componentDivId)?.getBoundingClientRect().width ?? 0;
  220. const trackWidth: number = width - 190 - 55;
  221. getRcsbFv(this.pfvDivId).updateBoardConfig({boardConfigData:{trackWidth:trackWidth}});
  222. }
  223. private select(selection: Array<SelectionInterface>): void{
  224. selection.forEach(d=>{
  225. const e: RcsbFvTrackDataElementInterface = d.rcsbFvTrackDataElement;
  226. const x = e.begin;
  227. const y = e.end ?? e.begin;
  228. if(e.isEmpty){
  229. this.props.plugin.select(
  230. [{modelId: this.currentModelId, asymId: this.currentLabelAsymId, position: x},{modelId: this.currentModelId, asymId: this.currentLabelAsymId, position: y}], 'select',
  231. 'add'
  232. );
  233. this.props.selection.addSelectionFromRegion(this.currentModelId, this.currentLabelAsymId, {begin:x, end:y, isEmpty: true, source: 'sequence'}, 'select');
  234. }else{
  235. this.props.plugin.select(this.currentModelId, this.currentLabelAsymId,x,y, 'select', 'add');
  236. this.props.selection.addSelectionFromRegion(this.currentModelId, this.currentLabelAsymId, {begin:x, end:y, source: 'sequence'}, 'select');
  237. }
  238. });
  239. }
  240. private resetPluginView(): void {
  241. this.props.plugin.clearFocus();
  242. this.props.plugin.resetCamera();
  243. }
  244. private async createComponents(modelMap:SaguaroPluginModelMapType): Promise<void> {
  245. await this.props.plugin.displayComponent("Water", false);
  246. await this.props.plugin.colorComponent("Polymer", 'entity-source');
  247. const chains: Array<{modelId: string; auth: string; label: string;}> = new Array<{modelId: string; auth: string; label: string;}>();
  248. modelMap.forEach((entry, modelId)=>{
  249. entry.chains.forEach(ch=>{
  250. if(ch.type === "polymer") {
  251. chains.push({modelId: modelId, auth: ch.auth, label: ch.label});
  252. }
  253. });
  254. });
  255. this.props.plugin.removeComponent();
  256. this.props.plugin.clearFocus();
  257. for(const ch of chains) {
  258. const label: string = ch.auth === ch.label ? ch.label : `${ch.label} [auth ${ch.auth}]`;
  259. await this.props.plugin.createComponent(label, ch.modelId, ch.label, 'cartoon');
  260. await this.props.plugin.colorComponent(label, 'entity-source');
  261. }
  262. /*this.props.plugin.pluginCall((plugin)=>{
  263. const createComponent = (label: string, tag: string, expression: Expression, representationType: StructureRepresentationRegistry.BuiltIn) => {
  264. return plugin.managers.structure.component.add({
  265. selection: StructureSelectionQuery(tag, expression),
  266. options: { checkExisting: false, label: label },
  267. representation: representationType,
  268. });
  269. }
  270. const recursive = (componentList: {label: string; tag: string; expression: Expression; representationType: StructureRepresentationRegistry.BuiltIn;}[])=>{
  271. if(componentList.length>0){
  272. const component = componentList.shift()!;
  273. createComponent(component.label, component.tag, component.expression, component.representationType).then(()=>{
  274. recursive(componentList);
  275. });
  276. }
  277. };
  278. recursive([{
  279. label: 'Ligands',
  280. tag: 'ligand',
  281. expression: Q.ligand.expression,
  282. representationType: 'ball-and-stick'
  283. },{
  284. label: 'Carbohydrates',
  285. tag: 'carbohydrate',
  286. expression: Q.branched.expression,
  287. representationType: 'carbohydrate'
  288. },{
  289. label: 'Ions',
  290. tag: 'ion',
  291. expression: Q.ion.expression,
  292. representationType: 'ball-and-stick'
  293. },{
  294. label: 'Lipids',
  295. tag: 'lipid',
  296. expression: Q.lipid.expression,
  297. representationType: 'ball-and-stick'
  298. }]);
  299. });*/
  300. await this.props.plugin.removeComponent("Polymer");
  301. }
  302. /*private removeComponents(labelAsymId?:string){
  303. if(labelAsymId != null){
  304. this.componentSet.get(labelAsymId)?.current.forEach(componentId=>{
  305. this.props.plugin.removeComponent(componentId);
  306. });
  307. }else{
  308. Array.from(this.componentSet.keys()).forEach(labelAsymId=>{
  309. this.componentSet.get(labelAsymId)?.current.forEach(componentId=>{
  310. this.props.plugin.removeComponent(componentId);
  311. });
  312. });
  313. }
  314. }
  315. private removeObsoleteComponents(): void{
  316. this.componentSet.get(this.currentLabelAsymId)?.previous.forEach(componentId=>{
  317. if(!this.componentSet.get(this.currentLabelAsymId)?.current.has(componentId)) {
  318. this.props.plugin.removeComponent(componentId);
  319. }
  320. });
  321. }
  322. private resetComponentKeys(): void {
  323. if(!this.componentSet.has(this.currentLabelAsymId))
  324. this.componentSet.set(this.currentLabelAsymId, {current: new Set<string>(), previous: new Set<string>()});
  325. this.componentSet.get(this.currentLabelAsymId)?.previous.clear();
  326. this.componentSet.get(this.currentLabelAsymId)?.current.forEach(e=>{
  327. this.componentSet.get(this.currentLabelAsymId)?.previous.add(e);
  328. });
  329. this.componentSet.get(this.currentLabelAsymId)?.current.clear();
  330. }*/
  331. }