RcsbFv3DComponent.tsx 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. import * as React from "react";
  2. import classes from '../styles/RcsbFvStyle.module.scss';
  3. import {StructureViewer} from '../RcsbFvStructure/StructureViewers/StructureViewer';
  4. import {StructureViewerInterface} from '../RcsbFvStructure/StructureViewerInterface';
  5. import '../styles/RcsbFvMolstarStyle.module.scss';
  6. import {RcsbFvSequence, RcsbFvSequenceInterface} from "../RcsbFvSequence/RcsbFvSequence";
  7. import {RcsbFvStructure, RcsbFvStructureConfigInterface} from "../RcsbFvStructure/RcsbFvStructure";
  8. import {
  9. EventType,
  10. RcsbFvContextManager,
  11. RcsbFvContextManagerInterface,
  12. UpdateConfigInterface
  13. } from "../RcsbFvContextManager/RcsbFvContextManager";
  14. import {Subscription} from "rxjs";
  15. import {PluginContext} from "molstar/lib/mol-plugin/context";
  16. import {RcsbFvSelectorManager} from "../RcsbFvState/RcsbFvSelectorManager";
  17. import {CSSProperties, MouseEvent} from "react";
  18. import {RcsbFvStateManager} from "../RcsbFvState/RcsbFvStateManager";
  19. import {StructureViewerBehaviourObserverInterface} from "../RcsbFvStructure/StructureViewerBehaviourInterface";
  20. export interface RcsbFv3DCssConfig {
  21. overwriteCss?: boolean;
  22. rootPanel?: CSSProperties;
  23. structurePanel?: CSSProperties;
  24. sequencePanel?: CSSProperties;
  25. }
  26. export interface RcsbFv3DComponentInterface<T,R,S,U> {
  27. structurePanelConfig:RcsbFvStructureConfigInterface<R,S>;
  28. sequencePanelConfig: RcsbFvSequenceInterface<T,R,U>;
  29. id: string;
  30. ctxManager: RcsbFvContextManager<T,R,S,U>;
  31. cssConfig?:RcsbFv3DCssConfig;
  32. unmount:(flag:boolean)=>void;
  33. fullScreen: boolean;
  34. structureViewer: StructureViewerInterface<R,S>;
  35. structureViewerBehaviourObserver: StructureViewerBehaviourObserverInterface<R>;
  36. }
  37. interface RcsbFv3DComponentState<T,R,S,U> {
  38. structurePanelConfig:RcsbFvStructureConfigInterface<R,S>;
  39. sequencePanelConfig:RcsbFvSequenceInterface<T,R,U>;
  40. pfvScreenFraction: number;
  41. }
  42. export class RcsbFv3DComponent<T,R,S,U> extends React.Component <RcsbFv3DComponentInterface<T,R,S,U>, RcsbFv3DComponentState<T,R,S,U>> {
  43. private readonly stateManager: RcsbFvStateManager = new RcsbFvStateManager();
  44. private subscription: Subscription;
  45. private readonly ROOT_DIV_ID: string = "rootPanelDiv";
  46. readonly state: RcsbFv3DComponentState<T,R,S,U> = {
  47. structurePanelConfig: this.props.structurePanelConfig,
  48. sequencePanelConfig: this.props.sequencePanelConfig,
  49. pfvScreenFraction: 0.55
  50. }
  51. render(): JSX.Element {
  52. return (
  53. <div className={this.props.fullScreen ? classes.fullScreen : classes.fullHeight} >
  54. <div
  55. id={this.ROOT_DIV_ID}
  56. style={RcsbFv3DComponent.mainDivCssConfig(this.props.cssConfig?.rootPanel)}
  57. className={this.useDefaultCss() ? classes.rcsbFvMain : ""}
  58. onMouseMove={(evt: MouseEvent<HTMLDivElement>)=>{this.mouseMove(evt)}}
  59. onMouseUp={ (e)=>{this.splitPanelMouseUp()} }
  60. >
  61. <div style={this.structureCssConfig(this.props.cssConfig?.structurePanel)} >
  62. <RcsbFvStructure<R,S>
  63. {...this.state.structurePanelConfig}
  64. componentId={this.props.id}
  65. structureViewer={this.props.structureViewer}
  66. stateManager={this.stateManager}
  67. structureViewerBehaviourObserver={this.props.structureViewerBehaviourObserver}
  68. />
  69. </div>
  70. <div style={this.sequenceCssConfig(this.props.cssConfig?.sequencePanel)} >
  71. <RcsbFvSequence<T,R,U>
  72. type={this.state.sequencePanelConfig.type}
  73. config={this.state.sequencePanelConfig.config}
  74. componentId={this.props.id}
  75. structureViewer={this.props.structureViewer}
  76. stateManager={this.stateManager}
  77. title={this.state.sequencePanelConfig.title}
  78. subtitle={this.state.sequencePanelConfig.subtitle}
  79. unmount={this.props.unmount}
  80. />
  81. </div>
  82. {
  83. this.panelDelimiter()
  84. }
  85. </div>
  86. </div>
  87. );
  88. }
  89. componentDidMount() {
  90. this.subscription = this.subscribe();
  91. }
  92. componentWillUnmount() {
  93. this.unsubscribe();
  94. }
  95. private useDefaultCss(): boolean {
  96. return this.state.sequencePanelConfig.type === "rcsb" || !this.props.cssConfig?.overwriteCss;
  97. }
  98. private panelDelimiter(): JSX.Element {
  99. return this.useDefaultCss() ? <div
  100. onMouseDown={() => {
  101. this.splitPanelMouseDown()
  102. }}
  103. className={classes.rcsbFvSplitPanel}
  104. style={{right: Math.round((1 - this.state.pfvScreenFraction) * 100) + "%"}}
  105. /> : <></>;
  106. }
  107. private structureCssConfig(css: CSSProperties | undefined): CSSProperties{
  108. const widthFr: number = Math.round((1-this.state.pfvScreenFraction)*100);
  109. const cssWidth: string = widthFr.toString()+"%";
  110. const cssHeight: string = "100%";
  111. return {...(this.useDefaultCss() ? {width:cssWidth, height:cssHeight, zIndex:100} : {}), ...css };
  112. }
  113. private sequenceCssConfig(css: CSSProperties | undefined): CSSProperties{
  114. const widthFr: number = Math.round((this.state.pfvScreenFraction)*100);
  115. const cssWidth: string = widthFr.toString()+"%";
  116. const cssHeight: string = "100%";
  117. return {...(this.useDefaultCss() ? {width:cssWidth, height:cssHeight, overflowY:"auto", overflowX:"hidden", paddingBottom:5} : {}), ...css };
  118. }
  119. private static mainDivCssConfig(css: CSSProperties | undefined): CSSProperties{
  120. return {...{
  121. }, ...css}
  122. }
  123. private subscribe(): Subscription{
  124. return this.props.ctxManager.subscribe((obj:RcsbFvContextManagerInterface<T,R,S,U>)=>{
  125. if(obj.eventType == EventType.UPDATE_CONFIG){
  126. this.updateConfig(obj.eventData as UpdateConfigInterface<T,R,S,U>)
  127. }else if(obj.eventType == EventType.PLUGIN_CALL){
  128. this.props.structureViewer.pluginCall(obj.eventData as ((f:PluginContext)=>void));
  129. }
  130. });
  131. }
  132. /**Unsubscribe className to rxjs events. Useful if many panels are created an destroyed.*/
  133. private unsubscribe(): void{
  134. this.subscription.unsubscribe();
  135. }
  136. private updateConfig(config:UpdateConfigInterface<T,R,S,U>){
  137. const structureConfig: Partial<RcsbFvStructureConfigInterface<R,S>> | undefined = config.structurePanelConfig;
  138. const sequenceConfig: Partial<RcsbFvSequenceInterface<T,R,U>> | undefined = config.sequencePanelConfig;
  139. if(structureConfig != null && sequenceConfig != null){
  140. this.setState({structurePanelConfig:{...this.state.structurePanelConfig, ...structureConfig}, sequencePanelConfig:{...this.state.sequencePanelConfig, ...sequenceConfig}});
  141. }else if(structureConfig != null){
  142. this.setState({structurePanelConfig:{...this.state.structurePanelConfig, ...structureConfig}});
  143. }else if(sequenceConfig != null){
  144. this.setState({sequencePanelConfig:{...this.state.sequencePanelConfig, ...sequenceConfig}});
  145. }
  146. }
  147. private splitPanelMouseDown(): void {
  148. const element: HTMLElement | null = document.getElementById(this.ROOT_DIV_ID);
  149. if(!element)return;
  150. element.style.cursor = "ew-resize";
  151. document.body.classList.add(classes.disableTextSelection);
  152. this.resize = (evt: MouseEvent<HTMLDivElement>)=>{
  153. const rect: DOMRect | undefined = element.getBoundingClientRect();
  154. const x: number = evt.clientX - rect.left;
  155. this.setState({pfvScreenFraction:x/rect.width});
  156. };
  157. }
  158. private splitPanelMouseUp(): void {
  159. if(typeof this.resize === "function") {
  160. const element: HTMLElement | null = document.getElementById(this.ROOT_DIV_ID);
  161. if (!element) return;
  162. element.style.cursor = "auto";
  163. document.body.classList.remove(classes.disableTextSelection);
  164. window.dispatchEvent(new Event('resize'));
  165. this.resize = null;
  166. }
  167. }
  168. private mouseMove(evt: MouseEvent<HTMLDivElement>): void{
  169. if(typeof this.resize === "function")
  170. this.resize(evt);
  171. }
  172. private resize: null | ((evt: MouseEvent<HTMLDivElement>)=>void) = null;
  173. }