RcsbFv3DComponent.tsx 8.5 KB

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