AssemblyView.tsx 17 KB

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