common.tsx 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  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 { State, StateTransform, StateTransformer, StateAction, StateObject } from '../../mol-state';
  7. import * as React from 'react';
  8. import { PurePluginUIComponent } from '../base';
  9. import { ParameterControls, ParamOnChange } from '../controls/parameters';
  10. import { PluginContext } from '../../mol-plugin/context';
  11. import { ParamDefinition as PD } from '../../mol-util/param-definition';
  12. import { Subject } from 'rxjs';
  13. import { Icon } from '../controls/icons';
  14. export { StateTransformParameters, TransformControlBase };
  15. class StateTransformParameters extends PurePluginUIComponent<StateTransformParameters.Props> {
  16. validate(params: any) {
  17. // TODO
  18. return void 0;
  19. }
  20. areInitial(params: any) {
  21. return PD.areEqual(this.props.info.params, params, this.props.info.initialValues);
  22. }
  23. onChange: ParamOnChange = ({ name, value }) => {
  24. const params = { ...this.props.params, [name]: value };
  25. this.props.events.onChange(params, this.areInitial(params), this.validate(params));
  26. };
  27. render() {
  28. return <ParameterControls params={this.props.info.params} values={this.props.params} onChange={this.onChange} onEnter={this.props.events.onEnter} isDisabled={this.props.isDisabled} />;
  29. }
  30. }
  31. namespace StateTransformParameters {
  32. export interface Props {
  33. info: {
  34. params: PD.Params,
  35. initialValues: any,
  36. isEmpty: boolean
  37. },
  38. events: {
  39. onChange: (params: any, areInitial: boolean, errors?: string[]) => void,
  40. onEnter: () => void,
  41. }
  42. params: any,
  43. isDisabled?: boolean,
  44. a?: StateObject,
  45. b?: StateObject
  46. }
  47. export type Class = React.ComponentClass<Props>
  48. function areParamsEmpty(params: PD.Params) {
  49. const keys = Object.keys(params);
  50. for (const k of keys) {
  51. if (!params[k].isHidden) return false;
  52. }
  53. return true;
  54. }
  55. export function infoFromAction(plugin: PluginContext, state: State, action: StateAction, nodeRef: StateTransform.Ref): Props['info'] {
  56. const source = state.cells.get(nodeRef)!.obj!;
  57. const params = action.definition.params ? action.definition.params(source, plugin) : { };
  58. const initialValues = PD.getDefaultValues(params);
  59. return {
  60. initialValues,
  61. params,
  62. isEmpty: areParamsEmpty(params)
  63. };
  64. }
  65. export function infoFromTransform(plugin: PluginContext, state: State, transform: StateTransform): Props['info'] {
  66. const cell = state.cells.get(transform.ref)!;
  67. // const source: StateObjectCell | undefined = (cell.sourceRef && state.cells.get(cell.sourceRef)!) || void 0;
  68. // const create = transform.transformer.definition.params;
  69. // const params = create ? create((source && source.obj) as any, plugin) : { };
  70. const params = (cell.params && cell.params.definition) || { };
  71. const initialValues = (cell.params && cell.params.values) || { };
  72. return {
  73. initialValues,
  74. params,
  75. isEmpty: areParamsEmpty(params)
  76. }
  77. }
  78. }
  79. namespace TransformControlBase {
  80. export interface ComponentState {
  81. params: any,
  82. error?: string,
  83. busy: boolean,
  84. isInitial: boolean,
  85. simpleOnly?: boolean,
  86. isCollapsed?: boolean
  87. }
  88. }
  89. abstract class TransformControlBase<P, S extends TransformControlBase.ComponentState> extends PurePluginUIComponent<P, S> {
  90. abstract applyAction(): Promise<void>;
  91. abstract getInfo(): StateTransformParameters.Props['info'];
  92. abstract getHeader(): StateTransformer.Definition['display'] | 'none';
  93. abstract canApply(): boolean;
  94. abstract getTransformerId(): string;
  95. abstract canAutoApply(newParams: any): boolean;
  96. abstract applyText(): string;
  97. abstract isUpdate(): boolean;
  98. abstract getSourceAndTarget(): { a?: StateObject, b?: StateObject };
  99. abstract state: S;
  100. private busy: Subject<boolean>;
  101. private onEnter = () => {
  102. if (this.state.error) return;
  103. this.apply();
  104. }
  105. private autoApplyHandle: number | undefined = void 0;
  106. private clearAutoApply() {
  107. if (this.autoApplyHandle !== void 0) {
  108. clearTimeout(this.autoApplyHandle);
  109. this.autoApplyHandle = void 0;
  110. }
  111. }
  112. events: StateTransformParameters.Props['events'] = {
  113. onEnter: this.onEnter,
  114. onChange: (params, isInitial, errors) => {
  115. this.clearAutoApply();
  116. this.setState({ params, isInitial, error: errors && errors[0] }, () => {
  117. if (!isInitial && !this.state.error && this.canAutoApply(params)) {
  118. this.clearAutoApply();
  119. this.autoApplyHandle = setTimeout(this.apply, 50) as any as number;
  120. }
  121. });
  122. }
  123. }
  124. apply = async () => {
  125. this.clearAutoApply();
  126. this.setState({ busy: true });
  127. try {
  128. await this.applyAction();
  129. } catch {
  130. // eat errors because they should be handled elsewhere
  131. } finally {
  132. this.busy.next(false);
  133. }
  134. }
  135. init() {
  136. this.busy = new Subject();
  137. this.subscribe(this.busy, busy => this.setState({ busy }));
  138. }
  139. refresh = () => {
  140. this.setState({ params: this.getInfo().initialValues, isInitial: true, error: void 0 });
  141. }
  142. setDefault = () => {
  143. const info = this.getInfo();
  144. const params = PD.getDefaultValues(info.params);
  145. this.setState({ params, isInitial: PD.areEqual(info.params, params, info.initialValues), error: void 0 });
  146. }
  147. toggleExpanded = () => {
  148. this.setState({ isCollapsed: !this.state.isCollapsed });
  149. }
  150. render() {
  151. const info = this.getInfo();
  152. const isEmpty = info.isEmpty && this.isUpdate();
  153. const display = this.getHeader();
  154. const tId = this.getTransformerId();
  155. const ParamEditor: StateTransformParameters.Class = this.plugin.customParamEditors.has(tId)
  156. ? this.plugin.customParamEditors.get(tId)!
  157. : StateTransformParameters;
  158. const wrapClass = this.state.isCollapsed
  159. ? 'msp-transform-wrapper msp-transform-wrapper-collapsed'
  160. : 'msp-transform-wrapper';
  161. const { a, b } = this.getSourceAndTarget();
  162. const showBack = this.isUpdate() && !(this.state.busy || this.state.isInitial);
  163. return <div className={wrapClass}>
  164. {display !== 'none' && <div className='msp-transform-header'>
  165. <button className={`msp-btn msp-btn-block${isEmpty ? '' : ' msp-btn-collapse'}`} onClick={this.toggleExpanded} title={display.description}>
  166. {!isEmpty && <Icon name={this.state.isCollapsed ? 'expand' : 'collapse'} />}
  167. {display.name}
  168. </button>
  169. </div>}
  170. {!isEmpty && !this.state.isCollapsed && <>
  171. <ParamEditor info={info} a={a} b={b} events={this.events} params={this.state.params} isDisabled={this.state.busy} />
  172. <div className='msp-transform-apply-wrap'>
  173. <button className='msp-btn msp-btn-block msp-transform-default-params' onClick={this.setDefault} disabled={this.state.busy} title='Set default params'><Icon name='cw' /></button>
  174. {showBack && <button className='msp-btn msp-btn-block msp-transform-refresh msp-form-control' title='Refresh params' onClick={this.refresh} disabled={this.state.busy || this.state.isInitial}>
  175. <Icon name='back' /> Back
  176. </button>}
  177. <div className={`msp-transform-apply${!showBack ? ' msp-transform-apply-wider' : ''}`}>
  178. <button className={`msp-btn msp-btn-block msp-btn-commit msp-btn-commit-${this.canApply() ? 'on' : 'off'}`} onClick={this.apply} disabled={!this.canApply()}>
  179. {this.canApply() && <Icon name='ok' />}
  180. {this.applyText()}
  181. </button>
  182. </div>
  183. </div>
  184. </>}
  185. </div>
  186. }
  187. }