plugin.tsx 10.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259
  1. /**
  2. * Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * @author David Sehnal <david.sehnal@gmail.com>
  5. * @author Alexander Rose <alexander.rose@weirdbyte.de>
  6. */
  7. import { List } from 'immutable';
  8. import * as React from 'react';
  9. import { PluginContext } from '../mol-plugin/context';
  10. import { formatTime } from '../mol-util';
  11. import { LogEntry } from '../mol-util/log-entry';
  12. import { PluginReactContext, PluginUIComponent } from './base';
  13. import { AnimationViewportControls, DefaultStructureTools, LociLabels, StateSnapshotViewportControls, TrajectoryViewportControls } from './controls';
  14. import { LeftPanelControls } from './left-panel';
  15. import { SequenceView } from './sequence';
  16. import { BackgroundTaskProgress } from './task';
  17. import { Toasts } from './toast';
  18. import { Viewport, ViewportControls } from './viewport';
  19. export class Plugin extends React.Component<{ plugin: PluginContext }, {}> {
  20. region(kind: 'left' | 'right' | 'bottom' | 'main', element: JSX.Element) {
  21. return <div className={`msp-layout-region msp-layout-${kind}`}>
  22. <div className='msp-layout-static'>
  23. {element}
  24. </div>
  25. </div>
  26. }
  27. render() {
  28. return <PluginReactContext.Provider value={this.props.plugin}>
  29. <Layout />
  30. </PluginReactContext.Provider>;
  31. }
  32. }
  33. export class PluginContextContainer extends React.Component<{ plugin: PluginContext }> {
  34. render() {
  35. return <PluginReactContext.Provider value={this.props.plugin}>
  36. <div className='msp-plugin'>
  37. {this.props.children}
  38. </div>
  39. </PluginReactContext.Provider>;
  40. }
  41. }
  42. type RegionKind = 'top' | 'left' | 'right' | 'bottom' | 'main'
  43. class Layout extends PluginUIComponent {
  44. componentDidMount() {
  45. this.subscribe(this.plugin.layout.events.updated, () => this.forceUpdate());
  46. }
  47. region(kind: RegionKind, Element?: React.ComponentClass) {
  48. return <div className={`msp-layout-region msp-layout-${kind}`}>
  49. <div className='msp-layout-static'>
  50. {Element ? <Element /> : null}
  51. </div>
  52. </div>;
  53. }
  54. get layoutVisibilityClassName() {
  55. const layout = this.plugin.layout.state;
  56. const controls = (this.plugin.spec.layout && this.plugin.spec.layout.controls) || { };
  57. const classList: string[] = []
  58. if (controls.top === 'none' || !layout.showControls || layout.regionState.top === 'hidden') {
  59. classList.push('msp-layout-hide-top')
  60. }
  61. if (controls.left === 'none' || !layout.showControls || layout.regionState.left === 'hidden') {
  62. classList.push('msp-layout-hide-left')
  63. } else if (layout.regionState.left === 'collapsed') {
  64. classList.push('msp-layout-collapse-left')
  65. }
  66. if (controls.right === 'none' || !layout.showControls || layout.regionState.right === 'hidden') {
  67. classList.push('msp-layout-hide-right')
  68. }
  69. if (controls.bottom === 'none' || !layout.showControls || layout.regionState.bottom === 'hidden') {
  70. classList.push('msp-layout-hide-bottom')
  71. }
  72. return classList.join(' ')
  73. }
  74. get layoutClassName() {
  75. const layout = this.plugin.layout.state;
  76. const classList: string[] = ['msp-plugin-content']
  77. if (layout.isExpanded) {
  78. classList.push('msp-layout-expanded')
  79. } else {
  80. classList.push('msp-layout-standard', `msp-layout-standard-${layout.controlsDisplay}`)
  81. }
  82. return classList.join(' ')
  83. }
  84. render() {
  85. const layout = this.plugin.layout.state;
  86. const controls = this.plugin.spec.layout?.controls || { };
  87. const viewport = this.plugin.spec.components?.viewport?.view || DefaultViewport;
  88. return <div className='msp-plugin'>
  89. <div className={this.layoutClassName}>
  90. <div className={this.layoutVisibilityClassName}>
  91. {this.region('main', viewport)}
  92. {layout.showControls && controls.top !== 'none' && this.region('top', controls.top || SequenceView)}
  93. {layout.showControls && controls.left !== 'none' && this.region('left', controls.left || LeftPanelControls)}
  94. {layout.showControls && controls.right !== 'none' && this.region('right', controls.right || ControlsWrapper)}
  95. {layout.showControls && controls.bottom !== 'none' && this.region('bottom', controls.bottom || Log)}
  96. </div>
  97. </div>
  98. </div>;
  99. }
  100. }
  101. export class ControlsWrapper extends PluginUIComponent {
  102. render() {
  103. const StructureTools = this.plugin.spec.components?.structureTools || DefaultStructureTools;
  104. return <div className='msp-scrollable-container'>
  105. {/* <CurrentObject /> */}
  106. <StructureTools />
  107. </div>;
  108. }
  109. }
  110. export class DefaultViewport extends PluginUIComponent {
  111. render() {
  112. const VPControls = this.plugin.spec.components?.viewport?.controls || ViewportControls;
  113. return <>
  114. <Viewport />
  115. <div className='msp-viewport-top-left-controls'>
  116. <AnimationViewportControls />
  117. <TrajectoryViewportControls />
  118. <StateSnapshotViewportControls />
  119. </div>
  120. <VPControls />
  121. <BackgroundTaskProgress />
  122. <div className='msp-highlight-toast-wrapper'>
  123. <LociLabels />
  124. <Toasts />
  125. </div>
  126. </>;
  127. }
  128. }
  129. export class Log extends PluginUIComponent<{}, { entries: List<LogEntry> }> {
  130. private wrapper = React.createRef<HTMLDivElement>();
  131. componentDidMount() {
  132. this.subscribe(this.plugin.events.log, () => this.setState({ entries: this.plugin.log.entries }));
  133. }
  134. componentDidUpdate() {
  135. this.scrollToBottom();
  136. }
  137. state = { entries: this.plugin.log.entries };
  138. private scrollToBottom() {
  139. const log = this.wrapper.current;
  140. if (log) log.scrollTop = log.scrollHeight - log.clientHeight - 1;
  141. }
  142. render() {
  143. // TODO: ability to show full log
  144. // showing more entries dramatically slows animations.
  145. const maxEntries = 10;
  146. const xs = this.state.entries, l = xs.size;
  147. const entries: JSX.Element[] = [];
  148. for (let i = Math.max(0, l - maxEntries), o = 0; i < l; i++) {
  149. const e = xs.get(i);
  150. entries.push(<li key={o++}>
  151. <div className={'msp-log-entry-badge msp-log-entry-' + e!.type} />
  152. <div className='msp-log-timestamp'>{formatTime(e!.timestamp)}</div>
  153. <div className='msp-log-entry'>{e!.message}</div>
  154. </li>);
  155. }
  156. return <div ref={this.wrapper} className='msp-log' style={{ position: 'absolute', top: '0', right: '0', bottom: '0', left: '0', overflowY: 'auto' }}>
  157. <ul className='msp-list-unstyled'>{entries}</ul>
  158. </div>;
  159. }
  160. }
  161. // export class CurrentObject extends PluginUIComponent {
  162. // get current() {
  163. // return this.plugin.state.behavior.currentObject.value;
  164. // }
  165. // componentDidMount() {
  166. // this.subscribe(this.plugin.state.behavior.currentObject, o => {
  167. // this.forceUpdate();
  168. // });
  169. // this.subscribe(this.plugin.behaviors.layout.leftPanelTabName, o => {
  170. // this.forceUpdate();
  171. // });
  172. // this.subscribe(this.plugin.events.state.object.updated, ({ ref, state }) => {
  173. // const current = this.current;
  174. // if (current.ref !== ref || current.state !== state) return;
  175. // this.forceUpdate();
  176. // });
  177. // }
  178. // render() {
  179. // const tabName = this.plugin.behaviors.layout.leftPanelTabName.value;
  180. // if (tabName !== 'data' && tabName !== 'settings') return null;
  181. // const current = this.current;
  182. // const ref = current.ref;
  183. // if (ref === StateTransform.RootRef) return null;
  184. // const cell = current.state.cells.get(ref)!;
  185. // const transform = cell.transform;
  186. // let showActions = true;
  187. // if (ref === StateTransform.RootRef) {
  188. // const children = current.state.tree.children.get(ref);
  189. // showActions = children.size !== 0;
  190. // }
  191. // if (!showActions) return null;
  192. // const actions = cell.status === 'ok' && <StateObjectActionSelect state={current.state} nodeRef={ref} plugin={this.plugin} />
  193. // if (cell.status === 'error') {
  194. // return <>
  195. // <SectionHeader icon='flow-cascade' title={`${cell.obj?.label || transform.transformer.definition.display.name}`} desc={transform.transformer.definition.display.name} />
  196. // <UpdateTransformControl state={current.state} transform={transform} customHeader='none' />
  197. // {actions}
  198. // </>;
  199. // }
  200. // if (cell.status !== 'ok') return null;
  201. // const decoratorChain = StateTreeSpine.getDecoratorChain(this.current.state, this.current.ref);
  202. // const parent = decoratorChain[decoratorChain.length - 1];
  203. // let decorators: JSX.Element[] | undefined = decoratorChain.length > 1 ? [] : void 0;
  204. // for (let i = decoratorChain.length - 2; i >= 0; i--) {
  205. // const d = decoratorChain[i];
  206. // decorators!.push(<ExpandGroup key={`${d.transform.transformer.id}-${i}`} header={d.transform.transformer.definition.display.name}>
  207. // <UpdateTransformControl state={current.state} transform={d.transform} customHeader='none' />
  208. // </ExpandGroup>);
  209. // }
  210. // return <>
  211. // <SectionHeader icon='flow-cascade' title={`${parent.obj?.label || parent.transform.transformer.definition.display.name}`} desc={parent.transform.transformer.definition.display.name} />
  212. // <UpdateTransformControl state={current.state} transform={parent.transform} customHeader='none' />
  213. // {decorators && <div className='msp-controls-section'>{decorators}</div>}
  214. // {actions}
  215. // </>;
  216. // }
  217. // }