diff --git a/src/mol-plugin/skin/base/components/temp.scss b/src/mol-plugin/skin/base/components/temp.scss index 801f23dafb710be39f183dc99d22963522803fb6..460be6060676d860f5751389a6bb9fec45577204 100644 --- a/src/mol-plugin/skin/base/components/temp.scss +++ b/src/mol-plugin/skin/base/components/temp.scss @@ -78,10 +78,31 @@ margin-bottom: 1px; padding-left: $row-height; padding-right: 2 * $row-height + $control-spacing; - border-bottom-left-radius: $control-spacing; + border-left: 1px dotted $entity-color-Group; // TODO custom color + // border-bottom-left-radius: $control-spacing; &-current { - background: $control-background + // background: $control-background + a { + color: $font-color; + } + + a:hover, a:hover > small { + color: color-lower-contrast($font-color, 24%); + } + } + + a { + display: block; + } + + a > small { + color: $font-color; + } + + a:hover { + font-weight: bold; + text-decoration: none; } } @@ -99,6 +120,7 @@ left: 0; top: 0; width: $row-height; + padding: 0; color: color-lower-contrast($font-color, 24%); } diff --git a/src/mol-plugin/skin/base/components/transformer.scss b/src/mol-plugin/skin/base/components/transformer.scss index f9d0105a7b49623289e0cb02f2eb5a36c6d9d51b..814640ef7e00ee4069d1833b71db224413f79130 100644 --- a/src/mol-plugin/skin/base/components/transformer.scss +++ b/src/mol-plugin/skin/base/components/transformer.scss @@ -10,7 +10,7 @@ } } -.msp-layout-right { +.msp-layout-right, .msp-layout-left { background: $control-background; } @@ -43,7 +43,8 @@ } .msp-transform-update-wrapper { - border-bottom: $control-spacing solid $control-background; + //border-bottom: $control-spacing solid $control-background; + margin-bottom: 1px; } .msp-transform-update-wrapper-collapsed { @@ -54,8 +55,9 @@ > .msp-transform-header > button { text-align: left; padding-left: $row-height; - background: $control-background; // color-lower-contrast($default-background, 4%); - font-weight: bold; + line-height: 24px; + background: color-lower-contrast($control-background, 4%); // $control-background; // color-lower-contrast($default-background, 4%); + // font-weight: bold; } } diff --git a/src/mol-plugin/state/actions/structure.ts b/src/mol-plugin/state/actions/structure.ts index 080fc02633a0970e403b10fded502ae574764613..b96e2d60fe0f0435c9079d7048d514f5f754a32b 100644 --- a/src/mol-plugin/state/actions/structure.ts +++ b/src/mol-plugin/state/actions/structure.ts @@ -89,8 +89,8 @@ export const OpenStructure = StateAction.build({ function createModelTree(b: StateBuilder.To<PluginStateObject.Data.Binary | PluginStateObject.Data.String>, format: 'pdb' | 'cif' = 'cif') { const parsed = format === 'cif' - ? b.apply(StateTransforms.Data.ParseCif, void 0, { props: { isGhost: true }}).apply(StateTransforms.Model.TrajectoryFromMmCif) - : b.apply(StateTransforms.Model.TrajectoryFromPDB); + ? b.apply(StateTransforms.Data.ParseCif, void 0, { props: { isGhost: true }}).apply(StateTransforms.Model.TrajectoryFromMmCif, void 0, { props: { isGhost: true }}) + : b.apply(StateTransforms.Model.TrajectoryFromPDB, void 0, { props: { isGhost: true }}); return parsed.apply(StateTransforms.Model.ModelFromTrajectory, { modelIndex: 0 }); } diff --git a/src/mol-plugin/state/transforms/model.ts b/src/mol-plugin/state/transforms/model.ts index 0211d19fd53e9cca8a15dcb3ba0985458d05d510..ea17a2729c0c30058e3bb1046a1f7e959608e8a2 100644 --- a/src/mol-plugin/state/transforms/model.ts +++ b/src/mol-plugin/state/transforms/model.ts @@ -85,7 +85,7 @@ const plus1 = (v: number) => v + 1, minus1 = (v: number) => v - 1; type ModelFromTrajectory = typeof ModelFromTrajectory const ModelFromTrajectory = PluginStateTransform.BuiltIn({ name: 'model-from-trajectory', - display: { name: 'Model from Trajectory', description: 'Create a molecular structure from the specified model.' }, + display: { name: 'Molecular Model', description: 'Create a molecular model from specified index in a trajectory.' }, from: SO.Molecule.Trajectory, to: SO.Molecule.Model, params: a => { @@ -99,7 +99,9 @@ const ModelFromTrajectory = PluginStateTransform.BuiltIn({ apply({ a, params }) { if (params.modelIndex < 0 || params.modelIndex >= a.data.length) throw new Error(`Invalid modelIndex ${params.modelIndex}`); const model = a.data[params.modelIndex]; - const props = { label: `Model ${model.modelNum}` }; + const props = a.data.length === 1 + ? { label: `${model.label}` } + : { label: `${model.label}:${model.modelNum}`, description: `Model ${model.modelNum} of ${a.data.length}` }; return new SO.Molecule.Model(model, props); } }); diff --git a/src/mol-plugin/ui/controls/parameters.tsx b/src/mol-plugin/ui/controls/parameters.tsx index 868c44f16e20e6ae927fe732b197831b73504546..b2d5e3bab971401f264ec0e2843059a1807e2eb6 100644 --- a/src/mol-plugin/ui/controls/parameters.tsx +++ b/src/mol-plugin/ui/controls/parameters.tsx @@ -31,7 +31,7 @@ export class ParameterControls<P extends PD.Params> extends React.PureComponent< const values = this.props.values; const keys = Object.keys(params); if (keys.length === 0) return null; - return <div style={{ width: '100%' }}> + return <> {keys.map(key => { const param = params[key]; if (param.isHidden) return null; @@ -39,7 +39,7 @@ export class ParameterControls<P extends PD.Params> extends React.PureComponent< if (!Control) return null; return <Control param={param} key={key} onChange={this.props.onChange} onEnter={this.props.onEnter} isDisabled={this.props.isDisabled} name={key} value={values[key]} /> })} - </div>; + </>; } } diff --git a/src/mol-plugin/ui/state/tree.tsx b/src/mol-plugin/ui/state/tree.tsx index b98a60577a573be0d405cef9ceac1c7504358403..812f1815cc67b82f149893487352b1cd9e9d6436 100644 --- a/src/mol-plugin/ui/state/tree.tsx +++ b/src/mol-plugin/ui/state/tree.tsx @@ -11,6 +11,7 @@ import { PluginCommands } from 'mol-plugin/command'; import { PluginUIComponent } from '../base'; import { UpdateTransformContol } from './update-transform'; import { StateObjectActions } from './actions'; +import { Observable, Subject } from 'rxjs'; export class StateTree extends PluginUIComponent<{ state: State }, { showActions: boolean }> { state = { showActions: true }; @@ -112,7 +113,10 @@ class StateTreeNode extends PluginUIComponent<{ nodeRef: string, state: State, d } } -class StateTreeNodeLabel extends PluginUIComponent<{ nodeRef: string, state: State, depth: number }, { state: State, isCurrent: boolean, isCollapsed: boolean }> { +class StateTreeNodeLabel extends PluginUIComponent< + { nodeRef: string, state: State, depth: number }, + { state: State, isCurrent: boolean, isCollapsed: boolean, updaterCollapsed: boolean }> { + is(e: State.ObjectEvent) { return e.ref === this.props.nodeRef && e.state === this.props.state; } @@ -142,7 +146,8 @@ class StateTreeNodeLabel extends PluginUIComponent<{ nodeRef: string, state: Sta state = { isCurrent: this.props.state.current === this.props.nodeRef, isCollapsed: this.props.state.cellStates.get(this.props.nodeRef).isCollapsed, - state: this.props.state + state: this.props.state, + updaterCollapsed: true } static getDerivedStateFromProps(props: { nodeRef: string, state: State }, state: { state: State, isCurrent: boolean, isCollapsed: boolean }) { @@ -150,12 +155,14 @@ class StateTreeNodeLabel extends PluginUIComponent<{ nodeRef: string, state: Sta return { isCurrent: props.state.current === props.nodeRef, isCollapsed: props.state.cellStates.get(props.nodeRef).isCollapsed, - state: props.state + state: props.state, + updaterCollapsed: true }; } setCurrent = (e: React.MouseEvent<HTMLElement>) => { e.preventDefault(); + e.currentTarget.blur(); PluginCommands.State.SetCurrentObject.dispatch(this.plugin, { state: this.props.state, ref: this.props.nodeRef }); } @@ -188,6 +195,13 @@ class StateTreeNodeLabel extends PluginUIComponent<{ nodeRef: string, state: Sta e.currentTarget.blur(); } + private toggleUpdaterObs = new Subject(); + toggleUpdater = (e: React.MouseEvent<HTMLAnchorElement>) => { + e.preventDefault(); + e.currentTarget.blur(); + this.toggleUpdaterObs.next(); + } + render() { const n = this.props.state.transforms.get(this.props.nodeRef)!; const cell = this.props.state.cells.get(this.props.nodeRef)!; @@ -207,9 +221,9 @@ class StateTreeNodeLabel extends PluginUIComponent<{ nodeRef: string, state: Sta const obj = cell.obj as PluginStateObject.Any; const title = `${obj.label} ${obj.description ? obj.description : ''}` if (this.state.isCurrent) { - label = <><b>{obj.label}</b> {obj.description ? <small>{obj.description}</small> : void 0}</>; + label = <><a title={title} href='#' onClick={this.toggleUpdater}><b>{obj.label}</b> {obj.description ? <small>{obj.description}</small> : void 0}</a></>; } else { - label = <><a title={title} href='#' onClick={this.setCurrent}>{obj.label}</a> {obj.description ? <small>{obj.description}</small> : void 0}</>; + label = <><a title={title} href='#' onClick={this.setCurrent}>{obj.label} {obj.description ? <small>{obj.description}</small> : void 0}</a></>; } } @@ -221,7 +235,7 @@ class StateTreeNodeLabel extends PluginUIComponent<{ nodeRef: string, state: Sta </button>; 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 }}> + style={{ marginLeft: this.state.isCurrent ? '0px' : `${this.props.depth * 10}px`, borderLeft: isCurrent || this.props.depth === 0 ? 'none' : void 0 }}> {label} {children.size > 0 && <button onClick={this.toggleExpanded} className='msp-btn msp-btn-link msp-tree-toggle-exp-button'> <span className={`msp-icon msp-icon-${cellState.isCollapsed ? 'expand' : 'collapse'}`} /> @@ -234,7 +248,7 @@ class StateTreeNodeLabel extends PluginUIComponent<{ nodeRef: string, state: Sta if (this.state.isCurrent) { return <> {row} - <StateTreeNodeTransform {...this.props}/> + <StateTreeNodeTransform {...this.props} toggleCollapsed={this.toggleUpdaterObs} /> </> } @@ -242,7 +256,7 @@ class StateTreeNodeLabel extends PluginUIComponent<{ nodeRef: string, state: Sta } } -class StateTreeNodeTransform extends PluginUIComponent<{ nodeRef: string, state: State, depth: number }, { isExpanded: boolean }> { +class StateTreeNodeTransform extends PluginUIComponent<{ nodeRef: string, state: State, depth: number, toggleCollapsed?: Observable<any> }> { render() { const ref = this.props.nodeRef; const cell = this.props.state.cells.get(ref)!; @@ -251,6 +265,6 @@ class StateTreeNodeTransform extends PluginUIComponent<{ nodeRef: string, state: if (!parent || parent.status !== 'ok') return null; const transform = cell.transform; - return <UpdateTransformContol state={this.props.state} transform={transform} initiallyCollapsed={true} />; + return <UpdateTransformContol state={this.props.state} transform={transform} initiallyCollapsed={true} toggleCollapsed={this.props.toggleCollapsed} />; } } \ No newline at end of file diff --git a/src/mol-plugin/ui/state/update-transform.tsx b/src/mol-plugin/ui/state/update-transform.tsx index 31f1a498ba9d8acbd859aae0b7b9c6370f73e3eb..6946089c6f928ff7828148c52be5747ff69fe0fe 100644 --- a/src/mol-plugin/ui/state/update-transform.tsx +++ b/src/mol-plugin/ui/state/update-transform.tsx @@ -7,6 +7,7 @@ import { State, StateTransform } from 'mol-state'; import { memoizeLatest } from 'mol-util/memoize'; import { StateTransformParameters, TransformContolBase } from './common'; +import { Observable } from 'rxjs'; export { UpdateTransformContol }; @@ -14,6 +15,7 @@ namespace UpdateTransformContol { export interface Props { transform: StateTransform, state: State, + toggleCollapsed?: Observable<any>, initiallyCollapsed?: boolean } @@ -43,12 +45,18 @@ class UpdateTransformContol extends TransformContolBase<UpdateTransformContol.Pr return autoUpdate({ a: cell.obj!, b: parentCell.obj!, oldParams: this.getInfo().initialValues, newParams }, this.plugin); } + componentDidMount() { + if (super.componentDidMount) super.componentDidMount(); + + if (this.props.toggleCollapsed) this.subscribe(this.props.toggleCollapsed, () => this.setState({ isCollapsed: !this.state.isCollapsed })); + } + private _getInfo = memoizeLatest((t: StateTransform) => StateTransformParameters.infoFromTransform(this.plugin, this.props.state, this.props.transform)); state: UpdateTransformContol.ComponentState = { transform: this.props.transform, error: void 0, isInitial: true, params: this.getInfo().initialValues, busy: false, isCollapsed: this.props.initiallyCollapsed }; static getDerivedStateFromProps(props: UpdateTransformContol.Props, state: UpdateTransformContol.ComponentState) { - if (props.transform === state.transform && props.initiallyCollapsed) return null; + if (props.transform === state.transform) return null; const cell = props.state.cells.get(props.transform.ref)!; const newState: Partial<UpdateTransformContol.ComponentState> = { transform: props.transform,