From fc3d19245ad04d45a35fdab51af26184ae28117b Mon Sep 17 00:00:00 2001 From: David Sehnal <david.sehnal@gmail.com> Date: Fri, 22 Feb 2019 19:36:54 +0100 Subject: [PATCH] mol-plugin: wip UI --- src/mol-plugin/ui/plugin.tsx | 42 ++++++++------------ src/mol-plugin/ui/state/actions.tsx | 35 +++++++++++++++++ src/mol-plugin/ui/state/common.tsx | 6 +-- src/mol-plugin/ui/state/tree.tsx | 59 ++++++++++++++++++++++++++--- 4 files changed, 107 insertions(+), 35 deletions(-) create mode 100644 src/mol-plugin/ui/state/actions.tsx diff --git a/src/mol-plugin/ui/plugin.tsx b/src/mol-plugin/ui/plugin.tsx index b78c717b4..f26503cd8 100644 --- a/src/mol-plugin/ui/plugin.tsx +++ b/src/mol-plugin/ui/plugin.tsx @@ -4,23 +4,21 @@ * @author David Sehnal <david.sehnal@gmail.com> */ +import { List } from 'immutable'; +import { PluginState } from 'mol-plugin/state'; +import { formatTime } from 'mol-util'; +import { LogEntry } from 'mol-util/log-entry'; import * as React from 'react'; import { PluginContext } from '../context'; -import { StateTree } from './state/tree'; -import { Viewport, ViewportControls } from './viewport'; -import { Controls, TrajectoryControls, LociLabelControl } from './controls'; -import { PluginUIComponent, PluginReactContext } from './base'; +import { PluginReactContext, PluginUIComponent } from './base'; import { CameraSnapshots } from './camera'; +import { Controls, LociLabelControl, TrajectoryControls } from './controls'; import { StateSnapshots } from './state'; -import { List } from 'immutable'; -import { LogEntry } from 'mol-util/log-entry'; -import { formatTime } from 'mol-util'; -import { BackgroundTaskProgress } from './task'; -import { ApplyActionContol } from './state/apply-action'; -import { PluginState } from 'mol-plugin/state'; -import { UpdateTransformContol } from './state/update-transform'; -import { StateObjectCell } from 'mol-state'; +import { StateObjectActions } from './state/actions'; import { AnimationControls } from './state/animation'; +import { StateTree } from './state/tree'; +import { BackgroundTaskProgress } from './task'; +import { Viewport, ViewportControls } from './viewport'; export class Plugin extends React.Component<{ plugin: PluginContext }, {}> { @@ -105,8 +103,8 @@ export class State extends PluginUIComponent { const kind = this.plugin.state.behavior.kind.value; return <div className='msp-scrollable-container'> <div className='msp-btn-row-group msp-data-beh'> - <button className='msp-btn msp-btn-block msp-form-control' onClick={() => this.set('data')} style={{ fontWeight: kind === 'data' ? 'bold' : 'normal'}}>Data</button> - <button className='msp-btn msp-btn-block msp-form-control' onClick={() => this.set('behavior')} style={{ fontWeight: kind === 'behavior' ? 'bold' : 'normal'}}>Behavior</button> + <button className='msp-btn msp-btn-block msp-form-control' onClick={() => this.set('data')} style={{ fontWeight: kind === 'data' ? 'bold' : 'normal' }}>Data</button> + <button className='msp-btn msp-btn-block msp-form-control' onClick={() => this.set('behavior')} style={{ fontWeight: kind === 'behavior' ? 'bold' : 'normal' }}>Behavior</button> </div> <StateTree state={kind === 'data' ? this.plugin.state.dataState : this.plugin.state.behaviorState} /> </div> @@ -172,21 +170,13 @@ export class CurrentObject extends PluginUIComponent { const current = this.current; const ref = current.ref; const cell = current.state.cells.get(ref)!; - const parent: StateObjectCell | undefined = (cell.sourceRef && current.state.cells.get(cell.sourceRef)!) || void 0; - const transform = cell.transform; const def = transform.transformer.definition; + const display = cell.obj ? cell.obj.label : (def.display && def.display.name) || def.name; - const actions = current.state.actions.fromCell(cell, this.plugin); - return <> - <div className='msp-current-header'> - {cell.obj ? cell.obj.label : (def.display && def.display.name) || def.name} - </div> - {(parent && parent.status === 'ok') && <UpdateTransformContol state={current.state} transform={transform} />} - {cell.status === 'ok' && <> - <div className='msp-section-header'>Actions</div> - {actions.map((act, i) => <ApplyActionContol plugin={this.plugin} key={`${act.id}`} state={current.state} action={act} nodeRef={ref} />)} - </>} + return cell.status === 'ok' && <> + <div className='msp-section-header'>{`Actions (${display})`}</div> + <StateObjectActions state={current.state} nodeRef={ref} /> </>; } } \ No newline at end of file diff --git a/src/mol-plugin/ui/state/actions.tsx b/src/mol-plugin/ui/state/actions.tsx new file mode 100644 index 000000000..2d660af3b --- /dev/null +++ b/src/mol-plugin/ui/state/actions.tsx @@ -0,0 +1,35 @@ +/** + * Copyright (c) 2018 - 2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author David Sehnal <david.sehnal@gmail.com> + */ + +import * as React from 'react'; +import { PluginUIComponent } from '../base'; +import { ApplyActionContol } from './apply-action'; +import { State } from 'mol-state'; + +export class StateObjectActions extends PluginUIComponent<{ state: State, nodeRef: string }> { + get current() { + return this.plugin.state.behavior.currentObject.value; + } + + componentDidMount() { + this.subscribe(this.plugin.state.behavior.currentObject, o => { + this.forceUpdate(); + }); + + this.subscribe(this.plugin.events.state.object.updated, ({ ref, state }) => { + const current = this.current; + if (current.ref !== ref || current.state !== state) return; + this.forceUpdate(); + }); + } + + render() { + const { state, nodeRef: ref } = this.props; + const cell = state.cells.get(ref)!; + const actions = state.actions.fromCell(cell, this.plugin); + return actions.map((act, i) => <ApplyActionContol plugin={this.plugin} key={`${act.id}`} state={state} action={act} nodeRef={ref} />); + } +} \ No newline at end of file diff --git a/src/mol-plugin/ui/state/common.tsx b/src/mol-plugin/ui/state/common.tsx index 22b484533..a1d39c7e8 100644 --- a/src/mol-plugin/ui/state/common.tsx +++ b/src/mol-plugin/ui/state/common.tsx @@ -166,7 +166,7 @@ abstract class TransformContolBase<P, S extends TransformContolBase.ControlState render() { const info = this.getInfo(); - if (info.isEmpty && this.isUpdate()) return null; + const isEmpty = info.isEmpty && this.isUpdate(); const display = this.getHeader(); @@ -178,9 +178,9 @@ abstract class TransformContolBase<P, S extends TransformContolBase.ControlState return <div className='msp-transform-wrapper'> <div className='msp-transform-header'> <button className='msp-btn msp-btn-block' onClick={this.toggleExpanded}>{display.name}</button> - {!this.state.isCollapsed && <button className='msp-btn msp-btn-link msp-transform-default-params' onClick={this.setDefault} disabled={this.state.busy} style={{ float: 'right'}} title='Set default params'>↻</button>} + {!isEmpty && !this.state.isCollapsed && <button className='msp-btn msp-btn-link msp-transform-default-params' onClick={this.setDefault} disabled={this.state.busy} style={{ float: 'right'}} title='Set default params'>↻</button>} </div> - {!this.state.isCollapsed && <> + {!isEmpty && !this.state.isCollapsed && <> <ParamEditor info={info} events={this.events} params={this.state.params} isDisabled={this.state.busy} /> <div className='msp-transform-apply-wrap'> diff --git a/src/mol-plugin/ui/state/tree.tsx b/src/mol-plugin/ui/state/tree.tsx index a4544a3b4..3859cb0a8 100644 --- a/src/mol-plugin/ui/state/tree.tsx +++ b/src/mol-plugin/ui/state/tree.tsx @@ -1,19 +1,44 @@ /** - * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2018 - 2019 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author David Sehnal <david.sehnal@gmail.com> */ import * as React from 'react'; import { PluginStateObject } from 'mol-plugin/state/objects'; -import { State, StateObject } from 'mol-state' +import { State, StateObject, StateObjectCell, StateTransform } from 'mol-state' import { PluginCommands } from 'mol-plugin/command'; import { PluginUIComponent } from '../base'; +import { UpdateTransformContol } from './update-transform'; +import { StateObjectActions } from './actions'; + +export class StateTree extends PluginUIComponent<{ state: State }, { showActions: boolean }> { + state = { showActions: true }; + + componentDidMount() { + this.subscribe(this.plugin.events.state.cell.created, e => { + if (e.cell.transform.parent === StateTransform.RootRef) this.forceUpdate(); + }); + + this.subscribe(this.plugin.events.state.cell.removed, e => { + if (e.parent === StateTransform.RootRef) this.forceUpdate(); + }); + } + + static getDerivedStateFromProps(props: { state: State }, state: { showActions: boolean }) { + const n = props.state.tree.root.ref; + const children = props.state.tree.children.get(n); + const showActions = children.size === 0; + if (state.showActions === showActions) return null; + return { showActions }; + } -export class StateTree extends PluginUIComponent<{ state: State }> { render() { - const n = this.props.state.tree.root.ref; - return <StateTreeNode state={this.props.state} nodeRef={n} depth={0} />; + const ref = this.props.state.tree.root.ref; + if (this.state.showActions) { + return <StateObjectActions state={this.props.state} nodeRef={ref} /> + } + return <StateTreeNode state={this.props.state} nodeRef={ref} depth={0} />; } } @@ -195,7 +220,7 @@ class StateTreeNodeLabel extends PluginUIComponent<{ nodeRef: string, state: Sta <span className='msp-icon msp-icon-visual-visibility' /> </button>; - return <div className={`msp-tree-row${isCurrent ? ' msp-tree-row-current' : ''}`} onMouseEnter={this.highlight} onMouseLeave={this.clearHighlight} + const row = <div className={`msp-tree-row${isCurrent ? ' msp-tree-row-current' : ''}`} onMouseEnter={this.highlight} onMouseLeave={this.clearHighlight} style={{ marginLeft: this.state.isCurrent ? '0px' : `${this.props.depth * 10}px`, borderRadius: this.state.isCurrent ? '0' : void 0 }}> {isCurrent ? <b>{label}</b> : label} {children.size > 0 && <button onClick={this.toggleExpanded} className='msp-btn msp-btn-link msp-tree-toggle-exp-button'> @@ -205,5 +230,27 @@ class StateTreeNodeLabel extends PluginUIComponent<{ nodeRef: string, state: Sta <span className='msp-icon msp-icon-remove' /> </button>}{visibility} </div>; + + if (this.state.isCurrent) { + return <> + {row} + <StateTreeNodeTransform {...this.props}/> + </> + } + + return row; + } +} + +class StateTreeNodeTransform extends PluginUIComponent<{ nodeRef: string, state: State, depth: number }, { isExpanded: boolean }> { + render() { + const ref = this.props.nodeRef; + const cell = this.props.state.cells.get(ref)!; + const parent: StateObjectCell | undefined = (cell.sourceRef && this.props.state.cells.get(cell.sourceRef)!) || void 0; + + if (!parent || parent.status !== 'ok') return null; + + const transform = cell.transform; + return <UpdateTransformContol state={this.props.state} transform={transform} />; } } \ No newline at end of file -- GitLab