CustomView.tsx 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232
  1. import {asyncScheduler} from "rxjs";
  2. import {AbstractView, AbstractViewInterface} from "./AbstractView";
  3. import {
  4. RcsbFvBoardConfigInterface,
  5. RcsbFvRowConfigInterface,
  6. RcsbFv,
  7. RcsbFvTrackDataElementInterface
  8. } from "@rcsb/rcsb-saguaro";
  9. import * as React from "react";
  10. import {RcsbFvSelectorManager} from "../../RcsbFvSelection/RcsbFvSelectorManager";
  11. import {
  12. SaguaroPluginModelMapType,
  13. SaguaroPluginPublicInterface
  14. } from "../../RcsbFvStructure/StructurePlugins/SaguaroPluginInterface";
  15. export type CustomViewStateInterface = Omit<CustomViewInterface, "modelChangeCallback">;
  16. export interface CustomViewInterface {
  17. blockConfig: FeatureBlockInterface | Array<FeatureBlockInterface>;
  18. blockSelectorElement?: (blockSelector: BlockSelectorManager) => JSX.Element;
  19. modelChangeCallback?: (modelMap: SaguaroPluginModelMapType) => CustomViewStateInterface;
  20. blockChangeCallback?: (plugin: SaguaroPluginPublicInterface, pfvList: Array<RcsbFv>, selection: RcsbFvSelectorManager) => void;
  21. }
  22. export interface FeatureBlockInterface {
  23. blockId:string;
  24. blockTitle?: string;
  25. blockShortName?: string;
  26. featureViewConfig: Array<FeatureViewInterface> | FeatureViewInterface;
  27. }
  28. export interface FeatureViewInterface {
  29. boardId?:string;
  30. boardConfig: RcsbFvBoardConfigInterface;
  31. rowConfig: Array<RcsbFvRowConfigInterface>;
  32. sequenceSelectionChangeCallback: (plugin: SaguaroPluginPublicInterface, selectorManager: RcsbFvSelectorManager, sequenceRegion: Array<RcsbFvTrackDataElementInterface>) => void;
  33. sequenceElementClickCallback: (plugin: SaguaroPluginPublicInterface, selectorManager: RcsbFvSelectorManager, d: RcsbFvTrackDataElementInterface) => void;
  34. sequenceHoverCallback: (plugin: SaguaroPluginPublicInterface, selectorManager: RcsbFvSelectorManager, hoverRegion: Array<RcsbFvTrackDataElementInterface>) => void;
  35. structureSelectionCallback: (plugin: SaguaroPluginPublicInterface, pfv: RcsbFv, selectorManager: RcsbFvSelectorManager) => void;
  36. structureHoverCallback: (plugin: SaguaroPluginPublicInterface, pfv: RcsbFv, selectorManager: RcsbFvSelectorManager) => void;
  37. }
  38. export class BlockSelectorManager {
  39. private blockId: string;
  40. private previousBlockId: string;
  41. private readonly blockChangeCallback: ()=>void = ()=>{};
  42. constructor(f:()=>void){
  43. this.blockChangeCallback = f;
  44. }
  45. setActiveBlock(blockId:string): void{
  46. this.previousBlockId = this.blockId;
  47. this.blockId = blockId;
  48. this.blockChangeCallback();
  49. }
  50. getActiveBlock(): string{
  51. return this.blockId;
  52. }
  53. getPreviousBlock(): string{
  54. return this.previousBlockId;
  55. }
  56. }
  57. export class CustomView extends AbstractView<CustomViewInterface & AbstractViewInterface, CustomViewStateInterface> {
  58. private blockViewSelector: BlockSelectorManager = new BlockSelectorManager( this.blockChange.bind(this) );
  59. private boardMap: Map<string, FeatureViewInterface> = new Map<string, FeatureViewInterface>();
  60. private blockMap: Map<string, Array<string>> = new Map<string, Array<string>>();
  61. private rcsbFvMap: Map<string, RcsbFv> = new Map<string, RcsbFv>();
  62. private firstModelLoad: boolean = true;
  63. private innerSelectionFlag: boolean = false;
  64. readonly state: CustomViewStateInterface = {
  65. blockConfig: this.props.blockConfig,
  66. blockSelectorElement: this.props.blockSelectorElement,
  67. blockChangeCallback: this.props.blockChangeCallback
  68. };
  69. constructor(props: CustomViewInterface & AbstractViewInterface) {
  70. super(props);
  71. this.mapBlocks(props.blockConfig);
  72. }
  73. componentDidMount(): void {
  74. super.componentDidMount();
  75. this.blockViewSelector.setActiveBlock( (this.state.blockConfig instanceof Array ? this.state.blockConfig : [this.state.blockConfig])[0].blockId! );
  76. }
  77. componentWillUnmount() {
  78. super.componentWillUnmount();
  79. this.rcsbFvMap.forEach((pfv,id)=>{
  80. pfv.unmount();
  81. });
  82. }
  83. private mapBlocks(config: FeatureBlockInterface | Array<FeatureBlockInterface>){
  84. this.blockMap.clear();
  85. this.boardMap.clear();
  86. ( config instanceof Array ? config : [config]).forEach(block=>{
  87. if(block.blockId == null)block.blockId = "block_"+Math.random().toString(36).substr(2);
  88. if(!this.blockMap.has(block.blockId))this.blockMap.set(block.blockId, new Array<string>());
  89. (block.featureViewConfig instanceof Array ? block.featureViewConfig : [block.featureViewConfig]).forEach(board=>{
  90. if(board.boardId == null)board.boardId = "board_"+Math.random().toString(36).substr(2);
  91. this.blockMap.get(block.blockId!)?.push(board.boardId);
  92. this.boardMap.set(board.boardId, board);
  93. });
  94. });
  95. }
  96. private blockChange(): void{
  97. this.unmountBlockFv();
  98. this.buildBlockFv();
  99. asyncScheduler.schedule(()=>{
  100. if(typeof this.state.blockChangeCallback === "function")
  101. this.state.blockChangeCallback(this.props.plugin, Array.from(this.blockMap.get(this.blockViewSelector.getActiveBlock())!.values()).map(boardId=>(this.rcsbFvMap.get(boardId)!)), this.props.selectorManager);
  102. else
  103. this.structureSelectionCallback();
  104. },1000);
  105. }
  106. private unmountBlockFv(){
  107. this.blockMap.get(this.blockViewSelector.getPreviousBlock())?.forEach(boardId=>{
  108. if(this.rcsbFvMap.get(boardId) == null)
  109. return;
  110. this.rcsbFvMap.get(boardId)!.unmount();
  111. document.getElementById("boardDiv_"+boardId)?.remove()
  112. });
  113. this.rcsbFvMap.clear();
  114. this.props.plugin.unsetCallbacks();
  115. }
  116. private buildBlockFv(){
  117. this.blockMap.get(this.blockViewSelector.getActiveBlock())?.forEach(boardId=>{
  118. if(this.boardMap.get(boardId) == null)
  119. return;
  120. const div: HTMLDivElement = document.createElement<"div">("div");
  121. div.setAttribute("id", "boardDiv_"+boardId);
  122. document.getElementById(this.componentDivId)?.append(div);
  123. const width: number = window.document.getElementById(this.componentDivId)?.getBoundingClientRect().width ?? 0;
  124. const trackWidth: number = width - (this.boardMap.get(boardId)!.boardConfig?.rowTitleWidth ?? 190) - 55;
  125. const rcsbFv: RcsbFv = new RcsbFv({
  126. elementId: "boardDiv_"+boardId,
  127. boardConfigData:{
  128. highlightHoverPosition:true,
  129. highlightHoverElement:true,
  130. ...this.boardMap.get(boardId)!.boardConfig,
  131. trackWidth:this.boardMap.get(boardId)!.boardConfig?.trackWidth ? this.boardMap.get(boardId)!.boardConfig?.trackWidth!-4 : trackWidth,
  132. selectionChangeCallBack:(selection: RcsbFvTrackDataElementInterface[])=>{
  133. if(this.innerSelectionFlag)
  134. return;
  135. this.boardMap.get(boardId)!.sequenceSelectionChangeCallback(this.props.plugin, this.props.selectorManager, selection);
  136. },
  137. highlightHoverCallback:(elements:Array<RcsbFvTrackDataElementInterface>)=>{
  138. this.boardMap.get(boardId)!.sequenceHoverCallback(this.props.plugin, this.props.selectorManager, elements);
  139. },
  140. elementClickCallBack: (element: RcsbFvTrackDataElementInterface)=>{
  141. this.boardMap.get(boardId)!.sequenceElementClickCallback(this.props.plugin, this.props.selectorManager, element);
  142. }
  143. },
  144. rowConfigData: this.boardMap.get(boardId)!.rowConfig
  145. });
  146. this.rcsbFvMap.set(boardId, rcsbFv);
  147. });
  148. this.props.plugin.setSelectCallback(()=>{
  149. this.structureSelectionCallback();
  150. });
  151. }
  152. structureSelectionCallback(): void {
  153. this.innerSelectionFlag = true;
  154. this.blockMap.get(this.blockViewSelector.getActiveBlock())?.forEach(boardId=>{
  155. const pfv: RcsbFv | undefined = this.rcsbFvMap.get(boardId);
  156. if(pfv == null)
  157. return;
  158. this.boardMap.get(boardId)?.structureSelectionCallback(this.props.plugin, pfv, this.props.selectorManager);
  159. });
  160. this.innerSelectionFlag = false;
  161. }
  162. structureHoverCallback(): void{
  163. this.blockMap.get(this.blockViewSelector.getActiveBlock())?.forEach(boardId=>{
  164. const pfv: RcsbFv | undefined = this.rcsbFvMap.get(boardId);
  165. if(pfv == null)
  166. return;
  167. this.boardMap.get(boardId)?.structureHoverCallback(this.props.plugin, pfv, this.props.selectorManager);
  168. });
  169. }
  170. representationChangeCallback(): void{
  171. //TODO
  172. }
  173. additionalContent(): JSX.Element {
  174. if(this.state.blockSelectorElement == null)
  175. return <></>;
  176. return this.state.blockSelectorElement(this.blockViewSelector);
  177. }
  178. modelChangeCallback(modelMap:SaguaroPluginModelMapType): void {
  179. if(this.firstModelLoad){
  180. this.firstModelLoad = false;
  181. return;
  182. }
  183. if(typeof this.props.modelChangeCallback === "function") {
  184. let newConfig: CustomViewStateInterface = this.props.modelChangeCallback(modelMap);
  185. if(newConfig != null ){
  186. newConfig = newConfig as CustomViewStateInterface;
  187. if(newConfig.blockConfig != null && newConfig.blockSelectorElement != null){
  188. this.mapBlocks(newConfig.blockConfig);
  189. this.setState({blockConfig: newConfig.blockConfig, blockSelectorElement: newConfig.blockSelectorElement})
  190. }else if(newConfig.blockConfig == null && newConfig.blockSelectorElement != null){
  191. this.setState({blockSelectorElement: newConfig.blockSelectorElement})
  192. }else if(newConfig.blockConfig != null && newConfig.blockSelectorElement == null){
  193. this.mapBlocks(newConfig.blockConfig);
  194. this.setState({blockConfig: newConfig.blockConfig})
  195. }
  196. }
  197. }
  198. }
  199. updateDimensions(): void {
  200. const div: HTMLElement | undefined | null = document.getElementById(this.componentDivId)?.parentElement;
  201. const width: number = window.document.getElementById(this.componentDivId)?.getBoundingClientRect().width ?? 0;
  202. if(div == null || (div.style.width && !div.style.width.includes("%")) )
  203. return;
  204. this.rcsbFvMap.forEach((rcsbFv, boardId)=>{
  205. const trackWidth: number = width - (this.boardMap.get(boardId)!.boardConfig?.rowTitleWidth ?? 190) - 55;
  206. rcsbFv.updateBoardConfig({boardConfigData:{trackWidth:this.boardMap.get(boardId)!.boardConfig?.trackWidth ? this.boardMap.get(boardId)!.boardConfig?.trackWidth!-4 : trackWidth}});
  207. });
  208. }
  209. }