diff --git a/src/mol-plugin/skin/base/components/controls.scss b/src/mol-plugin/skin/base/components/controls.scss index 367d6e783f7b7e05eb86eb949b5a82ea599898bf..f0fc1cbae0595c32c6a473f0f9abe25457571261 100644 --- a/src/mol-plugin/skin/base/components/controls.scss +++ b/src/mol-plugin/skin/base/components/controls.scss @@ -259,7 +259,7 @@ } } -.msp-conrol-group-expander { +.msp-control-group-expander { display: block; position: absolute; line-height: $row-height; @@ -312,7 +312,7 @@ .msp-help:hover span { display: inline-block; - background: linear-gradient($default-background, change-color($default-background, $alpha: 0.5)); + background: linear-gradient($default-background, change-color($default-background, $alpha: 0.8)); } .msp-help-text { diff --git a/src/mol-plugin/skin/base/components/temp.scss b/src/mol-plugin/skin/base/components/temp.scss index c0ea1796dddf3fb5876e03425e3eeee64d71406d..0f134557dba581c5255d334d738bcb94e8f9d35b 100644 --- a/src/mol-plugin/skin/base/components/temp.scss +++ b/src/mol-plugin/skin/base/components/temp.scss @@ -5,6 +5,7 @@ .msp-section-header { height: $row-height; line-height: $row-height; + margin-top: $control-spacing; margin-bottom: $control-spacing; text-align: right; padding: 0 $control-spacing; diff --git a/src/mol-plugin/skin/base/icons.scss b/src/mol-plugin/skin/base/icons.scss index 49162eda4eb043fc06bee6c41b35a9f767c3afa0..f5b669e2756bc25be1cc98b1ec4108554fcd0152 100644 --- a/src/mol-plugin/skin/base/icons.scss +++ b/src/mol-plugin/skin/base/icons.scss @@ -1,16 +1,13 @@ - [class^="msp-icon-"]:before, [class*=" msp-icon-"]:before { font-family: "fontello"; font-style: normal; font-weight: normal; - speak: none; display: inline-block; text-decoration: inherit; width: 1em; margin-right: .2em; text-align: center; - /* opacity: .8; */ /* For safety - reset parent styles, that can break glyph codes*/ font-variant: normal; @@ -23,15 +20,9 @@ /* remove if not needed */ margin-left: .2em; - /* you can be more comfortable with increased icons size */ - /* font-size: 120%; */ - /* Font smoothing. That was taken from TWBS */ -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; - - /* Uncomment for 3D effect */ - /* text-shadow: 1px 1px 1px rgba(127, 127, 127, 0.3); */ } .msp-icon-expand-layout:before { @@ -103,31 +94,31 @@ } .msp-icon-help:before { - content: '\e81c' + content: "\e81c"; } .msp-icon-help-circle:before { - content: '\e81d'; + content: "\e81d"; } .msp-icon-info:before { - content: '\e81e' + content: "\e81e"; } .msp-icon-left-open-big:before { - content: '\e87c' + content: "\e87c"; } .msp-icon-right-open-big:before { - content: '\e87d' + content: "\e87d"; } .msp-icon-left-open:before { - content: '\e874' + content: "\e874"; } .msp-icon-right-open:before { - content: '\e875' + content: "\e875"; } .msp-icon-screenshot:before { @@ -213,3 +204,19 @@ .msp-icon-tape:before { content: "\e8c8"; } + +.msp-icon-help-circle-expand { + width: 2.5em !important; +} +.msp-icon-help-circle-expand:before { + width: 2.5em !important; + content: "\e81d\0020\e885"; +} + +.msp-icon-help-circle-collapse { + width: 2.5em !important; +} +.msp-icon-help-circle-collapse:before { + width: 2.5em !important; + content: "\e81d\0020\e883"; +} \ No newline at end of file diff --git a/src/mol-plugin/ui/base.tsx b/src/mol-plugin/ui/base.tsx index afa47c47d7cf31e9b27ba82b054fbbcb94ecebcb..fb01a3ca69889ad31d6c02a695a2a0845fc59999 100644 --- a/src/mol-plugin/ui/base.tsx +++ b/src/mol-plugin/ui/base.tsx @@ -1,7 +1,8 @@ /** - * 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> + * @author Alexander Rose <alexander.rose@weirdbyte.de> */ import * as React from 'react'; @@ -61,4 +62,39 @@ export abstract class PurePluginUIComponent<P = {}, S = {}, SS = {}> extends Rea } export type _Props<C extends React.Component> = C extends React.Component<infer P> ? P : never -export type _State<C extends React.Component> = C extends React.Component<any, infer S> ? S : never \ No newline at end of file +export type _State<C extends React.Component> = C extends React.Component<any, infer S> ? S : never + +// + +export type CollapsableProps = { initiallyCollapsed?: boolean, header?: string } +export type CollapsableState = { isCollapsed: boolean, header: string } + +export abstract class CollapsableControls<P extends CollapsableProps = CollapsableProps, S extends CollapsableState = CollapsableState, SS = {}> extends PluginUIComponent<P, S, SS> { + toggleCollapsed = () => { + this.setState({ isCollapsed: !this.state.isCollapsed }) + } + + protected abstract defaultState(): S + protected abstract renderControls(): JSX.Element | null + + render() { + const wrapClass = this.state.isCollapsed + ? 'msp-transform-wrapper msp-transform-wrapper-collapsed' + : 'msp-transform-wrapper'; + + return <div className={wrapClass}> + <div className='msp-transform-header'> + <button className='msp-btn msp-btn-block' onClick={this.toggleCollapsed}> + <span className={`msp-icon msp-icon-${this.state.isCollapsed ? 'expand' : 'collapse'}`} /> + {this.state.header} + </button> + </div> + {!this.state.isCollapsed && this.renderControls()} + </div> + } + + constructor(props: P, context?: any) { + super(props, context) + this.state = this.defaultState() + } +} \ No newline at end of file diff --git a/src/mol-plugin/ui/controls/common.tsx b/src/mol-plugin/ui/controls/common.tsx index 9fd99992c3da223d28810e7b86f27540996690ed..46ca5ba4d38b675c140ce0285cb37f8a0cb6d724 100644 --- a/src/mol-plugin/ui/controls/common.tsx +++ b/src/mol-plugin/ui/controls/common.tsx @@ -122,7 +122,7 @@ export class ExpandableGroup extends React.Component<{ <div className='msp-control-row'> <span> {label} - <button className='msp-btn-link msp-btn-icon msp-conrol-group-expander' onClick={this.toggleExpanded} title={`${this.state.isExpanded ? 'Less' : 'More'} options`} + <button className='msp-btn-link msp-btn-icon msp-control-group-expander' onClick={this.toggleExpanded} title={`${this.state.isExpanded ? 'Less' : 'More'} options`} style={{ background: 'transparent', textAlign: 'left', padding: '0' }}> <span className={`msp-icon msp-icon-${this.state.isExpanded ? 'minus' : 'plus'}`} style={{ display: 'inline-block' }} /> </button> diff --git a/src/mol-plugin/ui/controls/parameters.tsx b/src/mol-plugin/ui/controls/parameters.tsx index 58ffcae56db8367902441b597df5b21cf7047558..3ef023cf8e0c5399c23524e644cfffc81406f1ee 100644 --- a/src/mol-plugin/ui/controls/parameters.tsx +++ b/src/mol-plugin/ui/controls/parameters.tsx @@ -129,9 +129,9 @@ export abstract class SimpleParam<P extends PD.Any> extends React.PureComponent< <span title={this.props.param.description}> {label} {hasHelp && - <button className='msp-help msp-btn-link msp-btn-icon msp-conrol-group-expander' onClick={this.toggleExpanded} title={`${this.state.isExpanded ? 'Hide' : 'Show'} help`} + <button className='msp-help msp-btn-link msp-btn-icon msp-control-group-expander' onClick={this.toggleExpanded} title={`${this.state.isExpanded ? 'Hide' : 'Show'} help`} style={{ background: 'transparent', textAlign: 'left', padding: '0' }}> - <span className={`msp-icon msp-icon-help-circle`} /> + <span className={`msp-icon msp-icon-help-circle-${this.state.isExpanded ? 'collapse' : 'expand'}`} /> </button> } </span> diff --git a/src/mol-plugin/ui/state/common.tsx b/src/mol-plugin/ui/state/common.tsx index bce39c8bd1877a52fe4df1dd5ca6793f0433875a..d2f1ef20306697014c718b97165b5a8f30bb0549 100644 --- a/src/mol-plugin/ui/state/common.tsx +++ b/src/mol-plugin/ui/state/common.tsx @@ -182,18 +182,13 @@ abstract class TransformControlBase<P, S extends TransformControlBase.ComponentS const wrapClass = this.state.isCollapsed ? 'msp-transform-wrapper msp-transform-wrapper-collapsed' : 'msp-transform-wrapper'; - // this.isUpdate() - // ? !isEmpty && !this.state.isCollapsed - // ? 'msp-transform-update-wrapper' - // : 'msp-transform-update-wrapper-collapsed' - // : 'msp-transform-wrapper'; const { a, b } = this.getSourceAndTarget(); return <div className={wrapClass}> <div className='msp-transform-header'> <button className='msp-btn msp-btn-block' onClick={this.toggleExpanded} title={display.description}> + <span className={`msp-icon msp-icon-${this.state.isCollapsed ? 'expand' : 'collapse'}`} /> {display.name} - {/* {!isEmpty && this.state.isCollapsed && this.isUpdate() && <small>Click to Edit</small>} */} </button> </div> {!isEmpty && !this.state.isCollapsed && <> diff --git a/src/mol-plugin/ui/structure/representation.tsx b/src/mol-plugin/ui/structure/representation.tsx index ba82755cbdf17a7e19a3483a4a26b21f95cb0f37..cbbb6e33da42d827a78300ddd0bcbc5d2300c643 100644 --- a/src/mol-plugin/ui/structure/representation.tsx +++ b/src/mol-plugin/ui/structure/representation.tsx @@ -15,6 +15,7 @@ import { ParamDefinition as PD } from '../../../mol-util/param-definition'; import { VisualQuality, VisualQualityOptions } from '../../../mol-geo/geometry/base'; import { StructureRepresentationPresets as P } from '../../util/structure-representation-helper'; import { camelCaseToWords } from '../../../mol-util/string'; +import { CollapsableControls } from '../base'; abstract class BaseStructureRepresentationControls extends PluginUIComponent { onChange = (value: string) => { @@ -85,7 +86,7 @@ class SelectionStructureRepresentationControls extends BaseStructureRepresentati } } -export class StructureRepresentationControls extends PluginUIComponent { +export class StructureRepresentationControls extends CollapsableControls { preset = async (value: string) => { const presetFn = P[value as keyof typeof P] if (presetFn) { @@ -121,15 +122,19 @@ export class StructureRepresentationControls extends PluginUIComponent { } } - render() { + defaultState() { + return { + isCollapsed: false, + header: 'Representation' + } + } + + renderControls() { const presets = Object.keys(P).map(name => { return [name, camelCaseToWords(name)] as [string, string] }) - return <div className='msp-transform-wrapper'> - <div className='msp-transform-header'> - <button className='msp-btn msp-btn-block'>Representation</button> - </div> + return <div> <div className='msp-control-row'> <div className='msp-select-row'> <ButtonSelect label='Preset' onChange={this.preset}> diff --git a/src/mol-plugin/ui/structure/selection.tsx b/src/mol-plugin/ui/structure/selection.tsx index ae3df5a7ca139041c1fa3aa9caa42b016b72a5c0..7a183cc06467db95ae55deb961184e0eb6fd233f 100644 --- a/src/mol-plugin/ui/structure/selection.tsx +++ b/src/mol-plugin/ui/structure/selection.tsx @@ -5,7 +5,7 @@ */ import * as React from 'react'; -import { PluginUIComponent } from '../base'; +import { CollapsableControls } from '../base'; import { StructureSelectionQueries, SelectionModifier } from '../../util/structure-selection-helper'; import { ButtonSelect, Options } from '../controls/common'; import { PluginCommands } from '../../command'; @@ -18,9 +18,7 @@ const StructureSelectionParams = { granularity: Interactivity.Params.granularity, } -export class StructureSelectionControls extends PluginUIComponent<{}, {}> { - state = {} - +export class StructureSelectionControls extends CollapsableControls { componentDidMount() { this.subscribe(this.plugin.events.interactivity.selectionUpdated, () => { this.forceUpdate() @@ -61,38 +59,40 @@ export class StructureSelectionControls extends PluginUIComponent<{}, {}> { remove = (value: string) => this.set('remove', value) only = (value: string) => this.set('only', value) - render() { + defaultState() { + return { + isCollapsed: false, + header: 'Selection' + } + } + + renderControls() { const queries = Object.keys(StructureSelectionQueries).map(name => { return [name, camelCaseToWords(name)] as [string, string] }) - return <div className='msp-transform-wrapper'> - <div className='msp-transform-header'> - <button className='msp-btn msp-btn-block'>Selection</button> + return <div> + <div className='msp-control-row msp-row-text'> + <div>{this.stats}</div> </div> - <div> - <div className='msp-control-row msp-row-text'> - <div>{this.stats}</div> - </div> - <ParameterControls params={StructureSelectionParams} values={this.values} onChange={this.setProps} /> - <div className='msp-control-row'> - <div className='msp-select-row'> - <ButtonSelect label='Add' onChange={this.add}> - <optgroup label='Add'> - {Options(queries)} - </optgroup> - </ButtonSelect> - <ButtonSelect label='Remove' onChange={this.remove}> - <optgroup label='Remove'> - {Options(queries)} - </optgroup> - </ButtonSelect> - <ButtonSelect label='Only' onChange={this.only}> - <optgroup label='Only'> - {Options(queries)} - </optgroup> - </ButtonSelect> - </div> + <ParameterControls params={StructureSelectionParams} values={this.values} onChange={this.setProps} /> + <div className='msp-control-row'> + <div className='msp-select-row'> + <ButtonSelect label='Add' onChange={this.add}> + <optgroup label='Add'> + {Options(queries)} + </optgroup> + </ButtonSelect> + <ButtonSelect label='Remove' onChange={this.remove}> + <optgroup label='Remove'> + {Options(queries)} + </optgroup> + </ButtonSelect> + <ButtonSelect label='Only' onChange={this.only}> + <optgroup label='Only'> + {Options(queries)} + </optgroup> + </ButtonSelect> </div> </div> </div>