state-tree.tsx 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. /**
  2. * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * @author David Sehnal <david.sehnal@gmail.com>
  5. */
  6. import * as React from 'react';
  7. import { PluginStateObject } from 'mol-plugin/state/objects';
  8. import { State, StateObject } from 'mol-state'
  9. import { PluginCommands } from 'mol-plugin/command';
  10. import { PluginComponent } from './base';
  11. export class StateTree extends PluginComponent<{ state: State }> {
  12. render() {
  13. const n = this.props.state.tree.root.ref;
  14. return <StateTreeNode state={this.props.state} nodeRef={n} />;
  15. }
  16. }
  17. class StateTreeNode extends PluginComponent<{ nodeRef: string, state: State }, { state: State, isCollapsed: boolean }> {
  18. is(e: State.ObjectEvent) {
  19. return e.ref === this.props.nodeRef && e.state === this.props.state;
  20. }
  21. get cellState() {
  22. return this.props.state.cellStates.get(this.props.nodeRef);
  23. }
  24. componentDidMount() {
  25. this.subscribe(this.plugin.events.state.cell.stateUpdated, e => {
  26. if (this.is(e) && e.state.transforms.has(this.props.nodeRef)) {
  27. this.setState({ isCollapsed: e.cellState.isCollapsed });
  28. }
  29. });
  30. this.subscribe(this.plugin.events.state.cell.created, e => {
  31. if (this.props.state === e.state && this.props.nodeRef === e.cell.transform.parent) {
  32. this.forceUpdate();
  33. }
  34. });
  35. this.subscribe(this.plugin.events.state.cell.removed, e => {
  36. if (this.props.state === e.state && this.props.nodeRef === e.parent) {
  37. this.forceUpdate();
  38. }
  39. });
  40. }
  41. state = {
  42. isCollapsed: this.props.state.cellStates.get(this.props.nodeRef).isCollapsed,
  43. state: this.props.state
  44. }
  45. static getDerivedStateFromProps(props: { nodeRef: string, state: State }, state: { state: State, isCollapsed: boolean }) {
  46. if (props.state === state.state) return null;
  47. return {
  48. isCollapsed: props.state.cellStates.get(props.nodeRef).isCollapsed,
  49. state: props.state
  50. };
  51. }
  52. render() {
  53. if (this.props.state.cells.get(this.props.nodeRef)!.obj === StateObject.Null) return null;
  54. const cellState = this.cellState;
  55. const children = this.props.state.tree.children.get(this.props.nodeRef);
  56. return <div>
  57. <StateTreeNodeLabel nodeRef={this.props.nodeRef} state={this.props.state} />
  58. {children.size === 0
  59. ? void 0
  60. : <div className='msp-tree-children' style={{ display: cellState.isCollapsed ? 'none' : 'block' }}>
  61. {children.map(c => <StateTreeNode state={this.props.state} nodeRef={c!} key={c} />)}
  62. </div>
  63. }
  64. </div>;
  65. }
  66. }
  67. class StateTreeNodeLabel extends PluginComponent<{ nodeRef: string, state: State }, { state: State, isCurrent: boolean, isCollapsed: boolean }> {
  68. is(e: State.ObjectEvent) {
  69. return e.ref === this.props.nodeRef && e.state === this.props.state;
  70. }
  71. componentDidMount() {
  72. this.subscribe(this.plugin.events.state.cell.stateUpdated, e => {
  73. if (this.is(e)) this.forceUpdate();
  74. });
  75. this.subscribe(this.plugin.state.behavior.currentObject, e => {
  76. if (!this.is(e)) {
  77. if (this.state.isCurrent && e.state.transforms.has(this.props.nodeRef)) {
  78. this.setState({ isCurrent: this.props.state.current === this.props.nodeRef });
  79. }
  80. return;
  81. }
  82. if (e.state.transforms.has(this.props.nodeRef)) {
  83. this.setState({
  84. isCurrent: this.props.state.current === this.props.nodeRef,
  85. isCollapsed: this.props.state.cellStates.get(this.props.nodeRef).isCollapsed
  86. });
  87. }
  88. });
  89. }
  90. state = {
  91. isCurrent: this.props.state.current === this.props.nodeRef,
  92. isCollapsed: this.props.state.cellStates.get(this.props.nodeRef).isCollapsed,
  93. state: this.props.state
  94. }
  95. static getDerivedStateFromProps(props: { nodeRef: string, state: State }, state: { state: State, isCurrent: boolean, isCollapsed: boolean }) {
  96. if (props.state === state.state) return null;
  97. return {
  98. isCurrent: props.state.current === props.nodeRef,
  99. isCollapsed: props.state.cellStates.get(props.nodeRef).isCollapsed,
  100. state: props.state
  101. };
  102. }
  103. setCurrent = (e: React.MouseEvent<HTMLElement>) => {
  104. e.preventDefault();
  105. PluginCommands.State.SetCurrentObject.dispatch(this.plugin, { state: this.props.state, ref: this.props.nodeRef });
  106. }
  107. remove = (e: React.MouseEvent<HTMLElement>) => {
  108. e.preventDefault();
  109. PluginCommands.State.RemoveObject.dispatch(this.plugin, { state: this.props.state, ref: this.props.nodeRef });
  110. }
  111. toggleVisible = (e: React.MouseEvent<HTMLElement>) => {
  112. e.preventDefault();
  113. PluginCommands.State.ToggleVisibility.dispatch(this.plugin, { state: this.props.state, ref: this.props.nodeRef });
  114. e.currentTarget.blur();
  115. }
  116. toggleExpanded = (e: React.MouseEvent<HTMLElement>) => {
  117. e.preventDefault();
  118. PluginCommands.State.ToggleExpanded.dispatch(this.plugin, { state: this.props.state, ref: this.props.nodeRef });
  119. e.currentTarget.blur();
  120. }
  121. highlight = (e: React.MouseEvent<HTMLElement>) => {
  122. e.preventDefault();
  123. PluginCommands.State.Highlight.dispatch(this.plugin, { state: this.props.state, ref: this.props.nodeRef });
  124. e.currentTarget.blur();
  125. }
  126. clearHighlight = (e: React.MouseEvent<HTMLElement>) => {
  127. e.preventDefault();
  128. PluginCommands.State.ClearHighlight.dispatch(this.plugin, { state: this.props.state, ref: this.props.nodeRef });
  129. e.currentTarget.blur();
  130. }
  131. render() {
  132. const n = this.props.state.transforms.get(this.props.nodeRef)!;
  133. const cell = this.props.state.cells.get(this.props.nodeRef)!;
  134. const isCurrent = this.is(this.props.state.behaviors.currentObject.value);
  135. let label: any;
  136. if (cell.status !== 'ok' || !cell.obj) {
  137. const name = n.transformer.definition.display.name;
  138. const title = `${cell.errorText}`
  139. label = <><b>{cell.status}</b> <a title={title} href='#' onClick={this.setCurrent}>{name}</a>: <i>{cell.errorText}</i></>;
  140. } else {
  141. const obj = cell.obj as PluginStateObject.Any;
  142. const title = `${obj.label} ${obj.description ? obj.description : ''}`
  143. label = <><a title={title} href='#' onClick={this.setCurrent}>{obj.label}</a> {obj.description ? <small>{obj.description}</small> : void 0}</>;
  144. }
  145. const children = this.props.state.tree.children.get(this.props.nodeRef);
  146. const cellState = this.props.state.cellStates.get(this.props.nodeRef);
  147. const remove = <button onClick={this.remove} className='msp-btn msp-btn-link msp-tree-remove-button'>
  148. <span className='msp-icon msp-icon-remove' />
  149. </button>;
  150. const visibility = <button onClick={this.toggleVisible} className={`msp-btn msp-btn-link msp-tree-visibility${cellState.isHidden ? ' msp-tree-visibility-hidden' : ''}`}>
  151. <span className='msp-icon msp-icon-visual-visibility' />
  152. </button>;
  153. return <div className={`msp-tree-row${isCurrent ? ' msp-tree-row-current' : ''}`} onMouseEnter={this.highlight} onMouseLeave={this.clearHighlight}>
  154. {isCurrent ? <b>{label}</b> : label}
  155. {children.size > 0 && <button onClick={this.toggleExpanded} className='msp-btn msp-btn-link msp-tree-toggle-exp-button'>
  156. <span className={`msp-icon msp-icon-${cellState.isCollapsed ? 'expand' : 'collapse'}`} />
  157. </button>}
  158. {remove}{visibility}
  159. </div>
  160. }
  161. }