diff --git a/src/mol-plugin-ui/base.tsx b/src/mol-plugin-ui/base.tsx index 68d90ce930282c5ea677f812dd707c75c5628377..aee65232b1f8560f4ed74760295e0dca79a61cd8 100644 --- a/src/mol-plugin-ui/base.tsx +++ b/src/mol-plugin-ui/base.tsx @@ -8,7 +8,7 @@ import * as React from 'react'; import { Observable, Subscription } from 'rxjs'; import { PluginContext } from '../mol-plugin/context'; -import { Icon } from './controls/icons'; +import { Button } from './controls/common'; export const PluginReactContext = React.createContext(void 0 as any as PluginContext); @@ -89,11 +89,10 @@ export abstract class CollapsableControls<P = {}, S = {}, SS = {}> extends Plugi return <div className={wrapClass}> <div className='msp-transform-header'> - <button className='msp-btn msp-form-control msp-btn-block msp-no-overflow' onClick={this.toggleCollapsed}> - <Icon name={this.state.isCollapsed ? 'expand' : 'collapse'} /> + <Button icon={this.state.isCollapsed ? 'expand' : 'collapse'} noOverflow onClick={this.toggleCollapsed}> {this.state.header} <small style={{ margin: '0 6px' }}>{this.state.isCollapsed ? '' : this.state.description}</small> - </button> + </Button> </div> {!this.state.isCollapsed && this.renderControls()} </div> diff --git a/src/mol-plugin-ui/camera.tsx b/src/mol-plugin-ui/camera.tsx index db0d87d70d8335bdc0a5418ba85dfcdf0d68d387..2afb16c971e0afda1b55f194d1394b5fdb1c9fbd 100644 --- a/src/mol-plugin-ui/camera.tsx +++ b/src/mol-plugin-ui/camera.tsx @@ -10,6 +10,7 @@ import { PluginUIComponent } from './base'; import { ParamDefinition as PD } from '../mol-util/param-definition'; import { ParameterControls } from './controls/parameters'; import { Icon } from './controls/icons'; +import { Button, IconButton } from './controls/common'; export class CameraSnapshots extends PluginUIComponent<{ }, { }> { render() { @@ -42,8 +43,8 @@ class CameraSnapshotControls extends PluginUIComponent<{ }, { name: string, desc <ParameterControls params={CameraSnapshotControls.Params} values={this.state} onEnter={this.add} onChange={p => this.setState({ [p.name]: p.value } as any)} /> <div className='msp-btn-row-group'> - <button className='msp-btn msp-btn-block msp-form-control' onClick={this.add}>Add</button> - <button className='msp-btn msp-btn-block msp-form-control' onClick={this.clear}>Clear</button> + <Button onClick={this.add}>Add</Button> + <Button onClick={this.clear}>Clear</Button> </div> </div>; } @@ -67,10 +68,8 @@ class CameraSnapshotList extends PluginUIComponent<{ }, { }> { render() { return <ul style={{ listStyle: 'none' }} className='msp-state-list'> {this.plugin.state.cameraSnapshots.state.entries.valueSeq().map(e =><li key={e!.id}> - <button className='msp-btn msp-btn-block msp-form-control' onClick={this.apply(e!.id)}>{e!.name || e!.timestamp} <small>{e!.description}</small></button> - <button onClick={this.remove(e!.id)} className='msp-btn msp-btn-link msp-state-list-remove-button'> - <Icon name='remove' /> - </button> + <Button onClick={this.apply(e!.id)}>{e!.name || e!.timestamp} <small>{e!.description}</small></Button> + <IconButton icon='remove' onClick={this.remove(e!.id)} className='msp-state-list-remove-button' /> </li>)} </ul>; } diff --git a/src/mol-plugin-ui/controls/action-menu.tsx b/src/mol-plugin-ui/controls/action-menu.tsx index e664e9e78b443e0372e5baecbe67079f0bb13d97..e2901607a856d291eef08d1e4b6990e676db76bb 100644 --- a/src/mol-plugin-ui/controls/action-menu.tsx +++ b/src/mol-plugin-ui/controls/action-menu.tsx @@ -5,9 +5,9 @@ */ import * as React from 'react' -import { Icon, IconName } from './icons'; +import { IconName } from './icons'; import { ParamDefinition } from '../../mol-util/param-definition'; -import { ControlGroup } from './common'; +import { ControlGroup, Button } from './common'; export class ActionMenu extends React.PureComponent<ActionMenu.Props> { hide = () => this.props.onSelect(void 0) @@ -185,18 +185,15 @@ class Section extends React.PureComponent<SectionProps, SectionState> { const { header, hasCurrent } = this.state; return <div className='msp-control-group-header msp-flex-row' style={{ marginTop: '1px' }}> - <button className='msp-btn msp-form-control msp-flex-item msp-no-overflow' onClick={this.toggleExpanded}> - <Icon name={this.state.isExpanded ? 'collapse' : 'expand'} /> + <Button icon={this.state.isExpanded ? 'collapse' : 'expand'} flex noOverflow onClick={this.toggleExpanded}> {hasCurrent ? <b>{header?.label}</b> : header?.label} - </button> - <button className='msp-btn msp-form-control msp-flex-item' onClick={this.selectAll} style={{ flex: '0 0 50px', textAlign: 'right' }}> - <Icon name='check' /> + </Button> + <Button icon='check' flex onClick={this.selectAll} style={{ flex: '0 0 50px', textAlign: 'right' }}> All - </button> - <button className='msp-btn msp-form-control msp-flex-item' onClick={this.selectNone} style={{ flex: '0 0 50px', textAlign: 'right' }}> - <Icon name='cancel' /> + </Button> + <Button icon='cancel' flex onClick={this.selectNone} style={{ flex: '0 0 50px', textAlign: 'right' }}> None - </button> + </Button> </div>; } @@ -204,10 +201,9 @@ class Section extends React.PureComponent<SectionProps, SectionState> { const { header, hasCurrent } = this.state; return <div className='msp-control-group-header' style={{ marginTop: '1px' }}> - <button className='msp-btn msp-btn-block msp-form-control msp-no-overflow' onClick={this.toggleExpanded}> - <Icon name={this.state.isExpanded ? 'collapse' : 'expand'} /> + <Button noOverflow icon={this.state.isExpanded ? 'collapse' : 'expand'} onClick={this.toggleExpanded}> {hasCurrent ? <b>{header?.label}</b> : header?.label} - </button> + </Button> </div>; } @@ -240,11 +236,10 @@ const Action: React.FC<{ const style: React.CSSProperties | undefined = item.addOn ? { position: 'relative' } : void 0; - return <button className='msp-btn msp-btn-block msp-form-control msp-action-menu-button msp-no-overflow' onClick={() => onSelect(multiselect ? [item] : item as any)} disabled={item.disabled} style={style}> - {item.icon && <Icon name={item.icon} />} + return <Button icon={item.icon} noOverflow className='msp-action-menu-button' onClick={() => onSelect(multiselect ? [item] : item as any)} disabled={item.disabled} style={style}> {isCurrent || item.selected ? <b>{item.label}</b> : item.label} {item.addOn} - </button>; + </Button>; } function isItems(x: any): x is ActionMenu.Items[] { diff --git a/src/mol-plugin-ui/controls/color.tsx b/src/mol-plugin-ui/controls/color.tsx index c6f2e4e023bb8f4e827f2137485cd1df610c5020..6cdc438f7f12df9cd8058c57d0002dfe15f5e18b 100644 --- a/src/mol-plugin-ui/controls/color.tsx +++ b/src/mol-plugin-ui/controls/color.tsx @@ -11,7 +11,7 @@ import { camelCaseToWords, stringToWords } from '../../mol-util/string'; import * as React from 'react'; import { _Props, _State } from '../base'; import { ParamProps } from './parameters'; -import { TextInput } from './common'; +import { TextInput, Button } from './common'; import { DefaultColorSwatch } from '../../mol-util/color/swatches'; export class CombinedColorControl extends React.PureComponent<ParamProps<PD.Color>, { isExpanded: boolean }> { @@ -43,8 +43,7 @@ export class CombinedColorControl extends React.PureComponent<ParamProps<PD.Colo swatch() { // const def = this.props.param.defaultValue; return <div className='msp-combined-color-swatch'> - {/* <button title='Default Color' key={def} className='msp-form-control msp-btn' data-color={def} onClick={this.onClickSwatch} style={{ background: Color.toStyle(def) }}></button> */} - {DefaultColorSwatch.map(c => <button key={c[1]} className='msp-form-control msp-btn' data-color={c[1]} onClick={this.onClickSwatch} style={{ background: Color.toStyle(c[1]) }}></button>)} + {DefaultColorSwatch.map(c => <Button key={c[1]} inline data-color={c[1]} onClick={this.onClickSwatch} style={{ background: Color.toStyle(c[1]) }} />)} </div>; } @@ -54,7 +53,7 @@ export class CombinedColorControl extends React.PureComponent<ParamProps<PD.Colo <div className='msp-control-row'> <span title={this.props.param.description}>{label}</span> <div> - <button onClick={this.toggleExpanded} className='msp-combined-color-button' style={{ background: Color.toStyle(this.props.value) }}></button> + <Button onClick={this.toggleExpanded} inline className='msp-combined-color-button' style={{ background: Color.toStyle(this.props.value) }} /> </div> </div> {this.state.isExpanded && <div className='msp-control-offset'> diff --git a/src/mol-plugin-ui/controls/common.tsx b/src/mol-plugin-ui/controls/common.tsx index 5c3dff74f10ffc01c0eda604325913296b599777..44c5c63c1faafca2bc53fb85a9cc5dec2c067894 100644 --- a/src/mol-plugin-ui/controls/common.tsx +++ b/src/mol-plugin-ui/controls/common.tsx @@ -6,7 +6,6 @@ import * as React from 'react'; import { Color } from '../../mol-util/color'; -import { PurePluginUIComponent } from '../base'; import { IconName, Icon } from './icons'; export class ControlGroup extends React.Component<{ @@ -33,11 +32,11 @@ export class ControlGroup extends React.Component<{ // TODO: customize header style (bg color, togle button etc) return <div className='msp-control-group-wrapper' style={{ position: 'relative', marginTop: this.props.noTopMargin ? 0 : void 0 }}> <div className='msp-control-group-header' style={{ marginLeft: this.props.headerLeftMargin }}> - <button className='msp-btn msp-form-control msp-btn-block' onClick={this.headerClicked}> + <Button onClick={this.headerClicked}> {!this.props.hideExpander && <Icon name={this.state.isExpanded ? 'collapse' : 'expand'} />} {this.props.topRightIcon && <Icon name={this.props.topRightIcon} style={{ position: 'absolute', right: '2px', top: 0 }} />} <b>{this.props.header}</b> - </button> + </Button> </div> {this.state.isExpanded && <div className={this.props.hideOffset ? '' : 'msp-control-offset'} style={{ display: this.state.isExpanded ? 'block' : 'none' }}> {this.props.children} @@ -71,7 +70,7 @@ interface TextInputState { function _id(x: any) { return x; } -export class TextInput<T = string> extends PurePluginUIComponent<TextInputProps<T>, TextInputState> { +export class TextInput<T = string> extends React.PureComponent<TextInputProps<T>, TextInputState> { private input = React.createRef<HTMLInputElement>(); private delayHandle: any = void 0; private pendingValue: T | undefined = void 0; @@ -224,7 +223,7 @@ export class NumericInput extends React.PureComponent<{ } } -export class ExpandableGroup extends React.Component<{ +export class ExpandableControlRow extends React.Component<{ label: string, colorStripe?: Color, pivot: JSX.Element, @@ -256,6 +255,54 @@ export class ExpandableGroup extends React.Component<{ } } +export function SectionHeader(props: { icon?: IconName, title: string | JSX.Element, desc?: string}) { + return <div className='msp-section-header'> + {props.icon && <Icon name={props.icon} />} + {props.title} <small>{props.desc}</small> + </div> +} + +export type ButtonProps = { + style?: React.CSSProperties, + className?: string, + disabled?: boolean, + title?: string, + icon?: IconName, + children?: React.ReactNode, + onClick?: (e: React.MouseEvent<HTMLButtonElement>) => void, + onContextMenu?: (e: React.MouseEvent<HTMLButtonElement>) => void, + onMouseEnter?: (e: React.MouseEvent<HTMLButtonElement>) => void, + onMouseLeave?: (e: React.MouseEvent<HTMLButtonElement>) => void, + inline?: boolean, + 'data-id'?: string, + flex?: boolean | string | number, + noOverflow?: boolean +} + +export function Button(props: ButtonProps) { + let className = 'msp-btn'; + if (!props.inline) className += ' msp-btn-block'; + if (props.noOverflow) className += ' msp-no-overflow'; + if (props.flex) className += ' msp-flex-item'; + if (props.className) className += ' ' + props.className; + + let style: React.CSSProperties | undefined = void 0; + if (props.flex) { + if (typeof props.flex === 'number') style = { flex: `0 0 ${props.flex}px`, padding: 0, maxWidth: `${props.flex}px` }; + else if (typeof props.flex === 'string') style = { flex: `0 0 ${props.flex}`, padding: 0, maxWidth: props.flex }; + } + if (props.style) { + if (style) Object.assign(style, props.style); + else style = props.style; + } + + return <button onClick={props.onClick} title={props.title} disabled={props.disabled} style={style} className={className} data-id={props['data-id']} + onContextMenu={props.onContextMenu} onMouseEnter={props.onMouseEnter} onMouseLeave={props.onMouseLeave}> + {props.icon && <Icon name={props.icon} />} + {props.children} + </button>; +} + export function IconButton(props: { icon: IconName, small?: boolean, @@ -263,13 +310,13 @@ export function IconButton(props: { title?: string, toggleState?: boolean, disabled?: boolean, - customClass?: string, + className?: string, style?: React.CSSProperties, 'data-id'?: string, extraContent?: JSX.Element, flex?: boolean | string | number }) { - let className = `msp-btn-link msp-btn-icon${props.small ? '-small' : ''}${props.customClass ? ' ' + props.customClass : ''}`; + let className = `msp-btn-link msp-btn-icon${props.small ? '-small' : ''}${props.className ? ' ' + props.className : ''}`; if (typeof props.toggleState !== 'undefined') { className += ` msp-btn-link-toggle-${props.toggleState ? 'on' : 'off'}` } @@ -286,38 +333,12 @@ export function IconButton(props: { else style = props.style; } - return <button className={className} onClick={props.onClick} title={props.title} disabled={props.disabled} data-id={props['data-id']} style={props.style}> + return <button className={className} onClick={props.onClick} title={props.title} disabled={props.disabled} data-id={props['data-id']} style={style}> <Icon name={props.icon} style={iconStyle} /> {props.extraContent} </button>; } -export class ButtonSelect extends React.PureComponent<{ label: string, onChange: (value: string) => void, disabled?: boolean }> { - onChange = (e: React.ChangeEvent<HTMLSelectElement>) => { - e.preventDefault() - this.props.onChange(e.target.value) - e.target.value = '_' - } - - render() { - return <select value='_' onChange={this.onChange} disabled={this.props.disabled}> - <option key='_' value='_'>{this.props.label}</option> - {this.props.children} - </select> - } -} - -export function Options(options: [string, string][]) { - return options.map(([value, label]) => <option key={value} value={value}>{label}</option>) -} - -export function SectionHeader(props: { icon?: IconName, title: string | JSX.Element, desc?: string}) { - return <div className='msp-section-header'> - {props.icon && <Icon name={props.icon} />} - {props.title} <small>{props.desc}</small> - </div> -} - export type ToggleButtonProps = { style?: React.CSSProperties, className?: string, diff --git a/src/mol-plugin-ui/custom/volume.tsx b/src/mol-plugin-ui/custom/volume.tsx index 939fb0fd7cce89c6fdaf2f7f2878aee8c55d87b1..14ef995f1555f1fe371de6d1afe138ea7a47acdf 100644 --- a/src/mol-plugin-ui/custom/volume.tsx +++ b/src/mol-plugin-ui/custom/volume.tsx @@ -8,7 +8,7 @@ import { PluginUIComponent } from '../base'; import { StateTransformParameters } from '../state/common'; import * as React from 'react'; import { VolumeStreaming } from '../../mol-plugin/behavior/dynamic/volume-streaming/behavior'; -import { ExpandableGroup } from '../controls/common'; +import { ExpandableControlRow } from '../controls/common'; import { ParamDefinition as PD } from '../../mol-util/param-definition'; import { ParameterControls, ParamOnChange } from '../controls/parameters'; import { Slider } from '../controls/slider'; @@ -38,12 +38,12 @@ function Channel(props: { const channel = props.channels[props.name]!; const { min, max, mean, sigma } = stats; - const value = Math.round(100 * (channel.isoValue.kind === 'relative' ? channel.isoValue.relativeValue : channel.isoValue.absoluteValue)) / 100; + const value = Math.round(100 * (channel.isoValue.kind === 'relative' ? channel.isoValue.relativeValue : channel.isoValue.absoluteValue)) / 100; const relMin = (min - mean) / sigma; const relMax = (max - mean) / sigma; const step = toPrecision(isRelative ? Math.round(((max - min) / sigma)) / 100 : sigma / 100, 2) - return <ExpandableGroup + return <ExpandableControlRow label={props.label + (props.isRelative ? ' \u03C3' : '')} colorStripe={channel.color} pivot={<Slider value={value} min={isRelative ? relMin : min} max={isRelative ? relMax : max} step={step} @@ -103,9 +103,11 @@ export class VolumeStreamingCustomControls extends PluginUIComponent<StateTransf }; convert(channel: any, stats: VolumeData['dataStats'], isRelative: boolean) { - return { ...channel, isoValue: isRelative - ? VolumeIsoValue.toRelative(channel.isoValue, stats) - : VolumeIsoValue.toAbsolute(channel.isoValue, stats) } + return { + ...channel, isoValue: isRelative + ? VolumeIsoValue.toRelative(channel.isoValue, stats) + : VolumeIsoValue.toAbsolute(channel.isoValue, stats) + } } changeOption: ParamOnChange = ({ name, value }) => { @@ -131,8 +133,8 @@ export class VolumeStreamingCustomControls extends PluginUIComponent<StateTransf ? old.entry.params.view.params : (((this.props.info.params as VolumeStreaming.ParamDefinition) .entry.map(old.entry.name) as PD.Group<VolumeStreaming.EntryParamDefinition>) - .params as VolumeStreaming.EntryParamDefinition) - .view.map(value.name).defaultValue; + .params as VolumeStreaming.EntryParamDefinition) + .view.map(value.name).defaultValue; const viewParams = { ...oldView }; if (value.name === 'selection-box') { @@ -187,7 +189,7 @@ export class VolumeStreamingCustomControls extends PluginUIComponent<StateTransf const OptionsParams = { entry: PD.Select(params.entry.name, b.data.entries.map(info => [info.dataId, info.dataId] as [string, string]), { isHidden: isOff, description: 'Which entry with volume data to display.' }), view: PD.MappedStatic(params.entry.params.view.name, { - 'off': PD.Group({ + 'off': PD.Group({ isRelative: PD.Boolean(isRelative, { isHidden: true }) }, { description: 'Display off.' }), 'box': PD.Group({ diff --git a/src/mol-plugin-ui/skin/base/components/misc.scss b/src/mol-plugin-ui/skin/base/components/misc.scss index ae43d0a3ee4ba818e6811dbf29da35102a44a848..3916bdaeea41c3ae65384380cc25781afde7c1c5 100644 --- a/src/mol-plugin-ui/skin/base/components/misc.scss +++ b/src/mol-plugin-ui/skin/base/components/misc.scss @@ -208,4 +208,12 @@ .msp-25-lower-contrast-text { color: color-lower-contrast($font-color, 25%); +} + +.msp-expandable-group-color-stripe { + position: absolute; + left: 0; + top: $row-height - 2px; + width: $control-label-width + $control-spacing; + height: 2px; } \ No newline at end of file diff --git a/src/mol-plugin-ui/state/animation.tsx b/src/mol-plugin-ui/state/animation.tsx index fbd19e6bc52e964054e736dbffffc279d8682343..6f523641c2fcf5d2f4f9c3c6632d6198fbcd2949 100644 --- a/src/mol-plugin-ui/state/animation.tsx +++ b/src/mol-plugin-ui/state/animation.tsx @@ -8,6 +8,7 @@ import * as React from 'react'; import { PluginUIComponent } from '../base'; import { ParameterControls, ParamOnChange } from '../controls/parameters'; import { Icon } from '../controls/icons'; +import { Button } from '../controls/common'; export class AnimationControlsWrapper extends PluginUIComponent<{ }> { render() { @@ -53,10 +54,9 @@ export class AnimationControls extends PluginUIComponent<{ onStart?: () => void <ParameterControls params={anim.current.params} values={anim.current.paramValues} onChange={this.updateCurrentParams} isDisabled={isDisabled} /> <div className='msp-btn-row-group'> - <button className='msp-btn msp-btn-block msp-form-control' onClick={this.startOrStop}> - {anim.state.animationState !== 'playing' && <Icon name='play' />} + <Button icon={anim.state.animationState !== 'playing' ? void 0 : 'play'} onClick={this.startOrStop}> {anim.state.animationState === 'playing' ? 'Stop' : 'Start'} - </button> + </Button> </div> </>; } diff --git a/src/mol-plugin-ui/state/snapshots.tsx b/src/mol-plugin-ui/state/snapshots.tsx index 4919d056f2ffabb7e948f58ded13730183671f4f..f7aa814acf32a567d8269f9d7ee5bb3a8af0cacd 100644 --- a/src/mol-plugin-ui/state/snapshots.tsx +++ b/src/mol-plugin-ui/state/snapshots.tsx @@ -13,10 +13,9 @@ import { ParameterControls } from '../controls/parameters'; import { ParamDefinition as PD} from '../../mol-util/param-definition'; import { PluginState } from '../../mol-plugin/state'; import { urlCombine } from '../../mol-util/url'; -import { IconButton, SectionHeader } from '../controls/common'; +import { IconButton, SectionHeader, Button } from '../controls/common'; import { formatTimespan } from '../../mol-util/now'; import { PluginConfig } from '../../mol-plugin/config'; -import { Icon } from '../controls/icons'; export class StateSnapshots extends PluginUIComponent<{ }> { downloadToFile = () => { @@ -37,7 +36,7 @@ export class StateSnapshots extends PluginUIComponent<{ }> { {this.plugin.spec.components?.remoteState !== 'none' && <RemoteStateSnapshots />} <div className='msp-btn-row-group' style={{ marginTop: '10px' }}> - <button className='msp-btn msp-btn-block msp-form-control' onClick={this.downloadToFile}>Download JSON</button> + <Button onClick={this.downloadToFile}>Download JSON</Button> <div className='msp-btn msp-btn-block msp-btn-action msp-loader-msp-btn-file'> {'Open JSON'} <input onChange={this.open} type='file' multiple={false} accept='.json' /> </div> @@ -95,10 +94,8 @@ class LocalStateSnapshots extends PluginUIComponent< }}/> <div className='msp-btn-row-group'> - {/* <button className='msp-btn msp-btn-block msp-form-control' onClick={this.add}><Icon name='floppy' /> Save</button> */} - <button className='msp-btn msp-btn-block msp-form-control' onClick={this.add}>Save</button> - {/* <button className='msp-btn msp-btn-block msp-form-control' onClick={this.upload} disabled={this.state.isUploading}>Upload</button> */} - <button className='msp-btn msp-btn-block msp-form-control' onClick={this.clear}>Clear</button> + <Button onClick={this.add}>Save</Button> + <Button onClick={this.clear}>Clear</Button> </div> </div>; } @@ -143,12 +140,12 @@ class LocalStateSnapshotList extends PluginUIComponent<{ }, { }> { const current = this.plugin.state.snapshots.state.current; return <ul style={{ listStyle: 'none' }} className='msp-state-list'> {this.plugin.state.snapshots.state.entries.map(e => <li key={e!.snapshot.id}> - <button data-id={e!.snapshot.id} className='msp-btn msp-btn-block msp-form-control' onClick={this.apply}> + <Button data-id={e!.snapshot.id} onClick={this.apply}> <span style={{ fontWeight: e!.snapshot.id === current ? 'bold' : void 0}}> {e!.name || new Date(e!.timestamp).toLocaleString()}</span> <small> {`${e!.snapshot.durationInMs ? formatTimespan(e!.snapshot.durationInMs, false) + `${e!.description ? ', ' : ''}` : ''}${e!.description ? e!.description : ''}`} </small> - </button> + </Button> <div> <IconButton data-id={e!.snapshot.id} icon='up-thin' title='Move Up' onClick={this.moveUp} small={true} /> <IconButton data-id={e!.snapshot.id} icon='down-thin' title='Move Down' onClick={this.moveDown} small={true} /> @@ -278,8 +275,8 @@ export class RemoteStateSnapshots extends PluginUIComponent< this.setState({ params: { ...this.state.params, [p.name]: p.value } } as any); }} isDisabled={this.state.isBusy}/> <div className='msp-btn-row-group'> - <button className='msp-btn msp-btn-block msp-form-control' onClick={this.upload} disabled={this.state.isBusy}><Icon name='upload' /> Upload</button> - <button className='msp-btn msp-btn-block msp-form-control' onClick={this.refresh} disabled={this.state.isBusy}>Refresh</button> + <Button icon='upload' onClick={this.upload} disabled={this.state.isBusy}>Upload</Button> + <Button onClick={this.refresh} disabled={this.state.isBusy}>Refresh</Button> </div> </>} @@ -291,7 +288,7 @@ export class RemoteStateSnapshots extends PluginUIComponent< this.setState({ params: { ...this.state.params, [p.name]: p.value } } as any); }} isDisabled={this.state.isBusy}/> <div className='msp-btn-row-group'> - <button className='msp-btn msp-btn-block msp-form-control' onClick={this.refresh} disabled={this.state.isBusy}>Refresh</button> + <Button onClick={this.refresh} disabled={this.state.isBusy}>Refresh</Button> </div> </>} </>; @@ -318,10 +315,10 @@ class RemoteStateSnapshotList extends PurePluginUIComponent< render() { return <ul style={{ listStyle: 'none' }} className='msp-state-list'> {this.props.entries.valueSeq().map(e =><li key={e!.id}> - <button data-id={e!.id} className='msp-btn msp-btn-block msp-form-control' onClick={this.props.fetch} + <Button data-id={e!.id} onClick={this.props.fetch} disabled={this.props.isBusy} onContextMenu={this.open} title='Click to download, right-click to open in a new tab.'> {e!.name || new Date(e!.timestamp).toLocaleString()} <small>{e!.description}</small> - </button> + </Button> {!e!.isSticky && this.props.remove && <div> <IconButton data-id={e!.id} icon='remove' title='Remove' onClick={this.props.remove} disabled={this.props.isBusy} /> </div>} diff --git a/src/mol-plugin-ui/structure/components.tsx b/src/mol-plugin-ui/structure/components.tsx index e9e0fc3bae2337f5498989ed59978ecd20ffad60..58c652cc1999c83afccafef096444c2a322ab395 100644 --- a/src/mol-plugin-ui/structure/components.tsx +++ b/src/mol-plugin-ui/structure/components.tsx @@ -12,8 +12,7 @@ import { State } from '../../mol-state'; import { ParamDefinition } from '../../mol-util/param-definition'; import { CollapsableControls, CollapsableState, PurePluginUIComponent } from '../base'; import { ActionMenu } from '../controls/action-menu'; -import { ExpandGroup, IconButton, ToggleButton } from '../controls/common'; -import { Icon } from '../controls/icons'; +import { ExpandGroup, IconButton, ToggleButton, Button } from '../controls/common'; import { ParameterControls } from '../controls/parameters'; import { UpdateTransformControl } from '../state/update-transform'; import { PluginContext } from '../../mol-plugin/context'; @@ -120,7 +119,7 @@ class ComponentEditorControls extends PurePluginUIComponent<{}, ComponentEditorC <ToggleButton icon='bookmarks' label='Preset' toggle={this.togglePreset} isSelected={this.state.action === 'preset'} disabled={this.isDisabled} /> <ToggleButton icon='plus' label='Add' toggle={this.toggleAdd} isSelected={this.state.action === 'add'} disabled={this.isDisabled} /> <ToggleButton icon='cog' label='' title='Options' style={{ flex: '0 0 40px', padding: 0 }} toggle={this.toggleOptions} isSelected={this.state.action === 'options'} disabled={this.isDisabled} /> - <IconButton customClass='msp-flex-item' flex='40px' onClick={this.undo} disabled={!this.state.canUndo || this.isDisabled} icon='back' title={undoTitle} /> + <IconButton className='msp-flex-item' flex='40px' onClick={this.undo} disabled={!this.state.canUndo || this.isDisabled} icon='back' title={undoTitle} /> </div> {this.state.action === 'preset' && this.presetControls} {this.state.action === 'add' && <div className='msp-control-offset'> @@ -166,9 +165,9 @@ class AddComponentControls extends PurePluginUIComponent<AddComponentControlsPro render() { return <> <ParameterControls params={this.state.params} values={this.state.values} onChangeValues={this.paramsChanged} /> - <button className={`msp-btn msp-btn-block msp-btn-commit msp-btn-commit-on`} onClick={this.apply} style={{ marginTop: '1px' }}> - <Icon name='plus' /> Create Selection - </button> + <Button icon='plus' className='msp-btn-commit msp-btn-commit-on' onClick={this.apply} style={{ marginTop: '1px' }}> + Create Selection + </Button> </>; } } @@ -347,9 +346,9 @@ class StructureComponentGroup extends PurePluginUIComponent<{ group: StructureCo {label} {/* <small className='msp-25-lower-contrast-text' style={{ float: 'right' }}>{reprLabel}</small> */} </button> - <IconButton onClick={this.toggleVisible} icon='visual-visibility' toggleState={!cell.state.isHidden} title={`${cell.state.isHidden ? 'Show' : 'Hide'} component`} small customClass='msp-form-control' flex /> - <IconButton onClick={this.toggleRemove} icon='remove' title='Remove' small toggleState={this.state.action === 'remove'} customClass='msp-form-control' flex /> - <IconButton onClick={this.toggleAction} icon='dot-3' title='Actions' toggleState={this.state.action === 'action'} customClass='msp-form-control' flex /> + <IconButton onClick={this.toggleVisible} icon='visual-visibility' toggleState={!cell.state.isHidden} title={`${cell.state.isHidden ? 'Show' : 'Hide'} component`} small className='msp-form-control' flex /> + <IconButton onClick={this.toggleRemove} icon='remove' title='Remove' small toggleState={this.state.action === 'remove'} className='msp-form-control' flex /> + <IconButton onClick={this.toggleAction} icon='dot-3' title='Actions' toggleState={this.state.action === 'action'} className='msp-form-control' flex /> </div> {this.state.action === 'remove' && <div style={{ marginBottom: '6px' }}> <ActionMenu items={this.removeActions} onSelect={this.selectRemoveAction} /> diff --git a/src/mol-plugin-ui/structure/focus.tsx b/src/mol-plugin-ui/structure/focus.tsx index f2df6df6dd55c6ad0871c06f8a02600ff27fd37f..2b869a82422bfccca3062529284dcefc6ef19549 100644 --- a/src/mol-plugin-ui/structure/focus.tsx +++ b/src/mol-plugin-ui/structure/focus.tsx @@ -6,7 +6,7 @@ import * as React from 'react'; import { PluginUIComponent } from '../base'; -import { ToggleButton, IconButton } from '../controls/common'; +import { ToggleButton, IconButton, Button } from '../controls/common'; import { ActionMenu } from '../controls/action-menu'; import { StructureElement, StructureProperties, Structure } from '../../mol-model/structure'; import { OrderedSet, SortedArray } from '../../mol-data/int'; @@ -187,11 +187,11 @@ export class StructureFocusControls extends PluginUIComponent<{}, StructureFocus return <> <div className='msp-control-row msp-select-row'> - <button className='msp-btn msp-btn-block msp-no-overflow' onClick={this.focus} title={title} onMouseEnter={this.highlightCurrent} onMouseLeave={this.clearHighlights} disabled={this.isDisabled || !current} + <Button noOverflow onClick={this.focus} title={title} onMouseEnter={this.highlightCurrent} onMouseLeave={this.clearHighlights} disabled={this.isDisabled || !current} style={{ textAlignLast: current ? 'left' : void 0 }}> {label} - </button> - {current && <IconButton onClick={this.clear} icon='cancel' title='Clear' customClass='msp-form-control' flex disabled={this.isDisabled} />} + </Button> + {current && <IconButton onClick={this.clear} icon='cancel' title='Clear' className='msp-form-control' flex disabled={this.isDisabled} />} <ToggleButton icon='book-open' title='Select Target' toggle={this.toggleAction} isSelected={this.state.showAction} disabled={this.isDisabled} style={{ flex: '0 0 40px', padding: 0 }} /> </div> {this.state.showAction && <ActionMenu items={this.actionItems} onSelect={this.selectAction} />} diff --git a/src/mol-plugin-ui/structure/generic.tsx b/src/mol-plugin-ui/structure/generic.tsx index 7107d0f012a45cfda249eb9663f9266232784be9..aa7ade924821e7d0825dd323b1eeb545c925cf79 100644 --- a/src/mol-plugin-ui/structure/generic.tsx +++ b/src/mol-plugin-ui/structure/generic.tsx @@ -139,8 +139,8 @@ export class GenericEntry<T extends HierarchyRef> extends PurePluginUIComponent< <button className='msp-form-control msp-control-button-label' title={`${label}. Click to focus.`} onClick={this.focus} onMouseEnter={this.highlight} onMouseLeave={this.clearHighlight} style={{ textAlign: 'left' }}> {label} <small>{description}</small> </button> - <IconButton customClass='msp-form-control' onClick={this.toggleVisibility} icon='visual-visibility' toggleState={!pivot.cell.state.isHidden} title={`${pivot.cell.state.isHidden ? 'Show' : 'Hide'}`} small flex /> - {refs.length === 1 && <IconButton customClass='msp-form-control' onClick={this.toggleOptions} icon='dot-3' title='Options' toggleState={this.state.showOptions} flex />} + <IconButton className='msp-form-control' onClick={this.toggleVisibility} icon='visual-visibility' toggleState={!pivot.cell.state.isHidden} title={`${pivot.cell.state.isHidden ? 'Show' : 'Hide'}`} small flex /> + {refs.length === 1 && <IconButton className='msp-form-control' onClick={this.toggleOptions} icon='dot-3' title='Options' toggleState={this.state.showOptions} flex />} </div> {(refs.length === 1 && this.state.showOptions) && <> <div className='msp-control-offset'> diff --git a/src/mol-plugin-ui/structure/measurements.tsx b/src/mol-plugin-ui/structure/measurements.tsx index 14c229de5fdc62e82331ba756fac5fcfab26daf5..24dc52a6efafd756c27e0d3b358d6fc390ef9023 100644 --- a/src/mol-plugin-ui/structure/measurements.tsx +++ b/src/mol-plugin-ui/structure/measurements.tsx @@ -15,7 +15,7 @@ import { angleLabel, dihedralLabel, distanceLabel, lociLabel } from '../../mol-t import { FiniteArray } from '../../mol-util/type-helpers'; import { PurePluginUIComponent } from '../base'; import { ActionMenu } from '../controls/action-menu'; -import { ExpandGroup, IconButton, ToggleButton } from '../controls/common'; +import { ExpandGroup, IconButton, ToggleButton, Button } from '../controls/common'; import { Icon } from '../controls/icons'; import { ParameterControls } from '../controls/parameters'; import { UpdateTransformControl } from '../state/update-transform'; @@ -138,12 +138,12 @@ export class MeasurementControls extends PurePluginUIComponent<{}, { isBusy: boo historyEntry(e: StructureSelectionHistoryEntry, idx: number) { const history = this.plugin.managers.structure.selection.additionsHistory; return <div className='msp-btn-row-group' key={e.id}> - <button className='msp-btn msp-btn-block msp-form-control msp-no-overflow' title='Click to focus. Hover to highlight.' onClick={() => this.focusLoci(e.loci)} style={{ width: 'auto', textAlign: 'left' }} onMouseEnter={() => this.highlight(e.loci)} onMouseLeave={this.plugin.managers.interactivity.lociHighlights.clearHighlights}> + <Button noOverflow title='Click to focus. Hover to highlight.' onClick={() => this.focusLoci(e.loci)} style={{ width: 'auto', textAlign: 'left' }} onMouseEnter={() => this.highlight(e.loci)} onMouseLeave={this.plugin.managers.interactivity.lociHighlights.clearHighlights}> {idx}. <span dangerouslySetInnerHTML={{ __html: e.label }} /> - </button> - {history.length > 1 && <IconButton small={true} customClass='msp-form-control' onClick={() => this.moveHistory(e, 'up')} icon='up-thin' flex='20px' title={'Move up'} />} - {history.length > 1 && <IconButton small={true} customClass='msp-form-control' onClick={() => this.moveHistory(e, 'down')} icon='down-thin' flex='20px' title={'Move down'} />} - <IconButton small={true} customClass='msp-form-control' onClick={() => this.plugin.managers.structure.selection.modifyHistory(e, 'remove')} icon='remove' flex title={'Remove'} /> + </Button> + {history.length > 1 && <IconButton small={true} className='msp-form-control' onClick={() => this.moveHistory(e, 'up')} icon='up-thin' flex='20px' title={'Move up'} />} + {history.length > 1 && <IconButton small={true} className='msp-form-control' onClick={() => this.moveHistory(e, 'down')} icon='down-thin' flex='20px' title={'Move down'} />} + <IconButton small={true} className='msp-form-control' onClick={() => this.plugin.managers.structure.selection.modifyHistory(e, 'remove')} icon='remove' flex title={'Remove'} /> </div>; } @@ -286,9 +286,9 @@ class MeasurementEntry extends PurePluginUIComponent<{ cell: StructureMeasuremen <button className='msp-form-control msp-control-button-label msp-no-overflow' title='Click to focus. Hover to highlight.' onClick={this.focus} style={{ width: 'auto', textAlign: 'left' }}> <span dangerouslySetInnerHTML={{ __html: this.label }} /> </button> - <IconButton small customClass='msp-form-control' onClick={this.toggleVisibility} icon='eye' flex title={cell.state.isHidden ? 'Show' : 'Hide'} toggleState={!cell.state.isHidden} /> - <IconButton small customClass='msp-form-control' onClick={this.delete} icon='remove' flex title='Delete' /> - <IconButton customClass='msp-form-control' onClick={this.toggleUpdate} icon='dot-3' flex title='Actions' toggleState={this.state.showUpdate} /> + <IconButton small className='msp-form-control' onClick={this.toggleVisibility} icon='eye' flex title={cell.state.isHidden ? 'Show' : 'Hide'} toggleState={!cell.state.isHidden} /> + <IconButton small className='msp-form-control' onClick={this.delete} icon='remove' flex title='Delete' /> + <IconButton className='msp-form-control' onClick={this.toggleUpdate} icon='dot-3' flex title='Actions' toggleState={this.state.showUpdate} /> </div> {this.state.showUpdate && <> <ActionMenu items={this.actions} onSelect={this.selectAction} /> diff --git a/src/mol-plugin-ui/structure/selection.tsx b/src/mol-plugin-ui/structure/selection.tsx index 94f01235b65c76b93522dcc288c357a401ee756a..10ec7d2121b0dca335a8eb5f6e4c0a889ec2022b 100644 --- a/src/mol-plugin-ui/structure/selection.tsx +++ b/src/mol-plugin-ui/structure/selection.tsx @@ -16,8 +16,7 @@ import { ParamDefinition } from '../../mol-util/param-definition'; import { stripTags } from '../../mol-util/string'; import { CollapsableControls, CollapsableState, PurePluginUIComponent } from '../base'; import { ActionMenu } from '../controls/action-menu'; -import { ControlGroup, ToggleButton, IconButton } from '../controls/common'; -import { Icon } from '../controls/icons'; +import { ControlGroup, ToggleButton, IconButton, Button } from '../controls/common'; import { ParameterControls } from '../controls/parameters'; import { StructureMeasurementsControls } from './measurements'; @@ -169,11 +168,11 @@ export class StructureSelectionControls<P, S extends StructureSelectionControlsS <ParameterControls params={StructureSelectionParams} values={this.values} onChangeValues={this.setProps} /> {this.controls} <div className='msp-control-row msp-select-row' style={{ margin: '6px 0' }}> - <button className='msp-btn msp-btn-block msp-no-overflow' onClick={this.focus} title='Click to Focus Selection' disabled={empty} + <Button noOverflow onClick={this.focus} title='Click to Focus Selection' disabled={empty} style={{ textAlignLast: !empty ? 'left' : void 0 }}> {this.stats} - </button> - {!empty && <IconButton onClick={this.clear} icon='cancel' title='Clear' customClass='msp-form-control' flex />} + </Button> + {!empty && <IconButton onClick={this.clear} icon='cancel' title='Clear' className='msp-form-control' flex />} </div> <StructureMeasurementsControls /> </> @@ -204,9 +203,9 @@ class ApplyColorControls extends PurePluginUIComponent<ApplyColorControlsProps, render() { return <> <ParameterControls params={this.params} values={this.state.values} onChangeValues={this.paramsChanged} /> - <button className={`msp-btn msp-btn-block msp-btn-commit msp-btn-commit-on`} onClick={this.apply} style={{ marginTop: '1px' }}> - <Icon name='brush' /> Apply Coloring - </button> + <Button icon='brush' className='msp-btn-commit msp-btn-commit-on' onClick={this.apply} style={{ marginTop: '1px' }}> + Apply Coloring + </Button> </>; } } \ No newline at end of file diff --git a/src/mol-plugin-ui/structure/source.tsx b/src/mol-plugin-ui/structure/source.tsx index b69237afc8eb64ddc56689dc42c05dc516bd3798..9c1f427ef073861efbeccfddda64cfdb8c8a5fa7 100644 --- a/src/mol-plugin-ui/structure/source.tsx +++ b/src/mol-plugin-ui/structure/source.tsx @@ -9,7 +9,7 @@ import * as React from 'react'; import { HierarchyRef, ModelRef, TrajectoryRef } from '../../mol-plugin-state/manager/structure/hierarchy-state'; import { CollapsableControls, CollapsableState } from '../base'; import { ActionMenu } from '../controls/action-menu'; -import { IconButton } from '../controls/common'; +import { IconButton, Button } from '../controls/common'; import { ParameterControls } from '../controls/parameters'; import { PluginCommands } from '../../mol-plugin/commands'; import { StateTransforms } from '../../mol-plugin-state/transforms'; @@ -252,10 +252,8 @@ export class StructureSourceControls extends CollapsableControls<{}, StructureSo const label = this.label; return <> <div className='msp-btn-row-group' style={{ marginTop: '1px' }}> - <button className='msp-btn msp-form-control msp-flex-item msp-no-overflow' onClick={this.toggleHierarchy} style={{ overflow: 'hidden', textOverflow: 'ellipsis' }} disabled={disabled} title={label}> - {label} - </button> - {presets.length > 0 && <IconButton customClass='msp-form-control' flex onClick={this.togglePreset} icon='bookmarks' title='Presets' toggleState={this.state.show === 'presets'} disabled={disabled} />} + <Button noOverflow flex onClick={this.toggleHierarchy} disabled={disabled} title={label}>{label}</Button> + {presets.length > 0 && <IconButton className='msp-form-control' flex onClick={this.togglePreset} icon='bookmarks' title='Presets' toggleState={this.state.show === 'presets'} disabled={disabled} />} </div> {this.state.show === 'hierarchy' && <ActionMenu items={this.hierarchyItems} onSelect={this.selectHierarchy} multiselect />} {this.state.show === 'presets' && <ActionMenu items={presets} onSelect={this.applyPreset} />} diff --git a/src/mol-plugin-ui/viewport/help.tsx b/src/mol-plugin-ui/viewport/help.tsx index 4633f37bbad07c8a3e9442410fb6b500edc51b40..8336809f07d36f5701891e387fe14fac754f021b 100644 --- a/src/mol-plugin-ui/viewport/help.tsx +++ b/src/mol-plugin-ui/viewport/help.tsx @@ -11,6 +11,7 @@ import { StateTransformer, StateSelection } from '../../mol-state'; import { SelectLoci } from '../../mol-plugin/behavior/dynamic/representation'; import { FocusLoci } from '../../mol-plugin/behavior/dynamic/representation'; import { Icon } from '../controls/icons'; +import { Button } from '../controls/common'; function getBindingsList(bindings: { [k: string]: Binding }) { return Object.keys(bindings).map(k => [k, bindings[k]] as [string, Binding]) @@ -55,10 +56,10 @@ class HelpGroup extends React.PureComponent<{ header: string, initiallyExpanded? render() { return <div className='msp-control-group-wrapper'> <div className='msp-control-group-header'> - <button className='msp-btn msp-btn-block' onClick={this.toggleExpanded}> + <Button onClick={this.toggleExpanded}> <Icon name={this.state.isExpanded ? 'collapse' : 'expand'} /> {this.props.header} - </button> + </Button> </div> {this.state.isExpanded && <div className='msp-control-offset' style={{ display: this.state.isExpanded ? 'block' : 'none' }}> {this.props.children} diff --git a/src/mol-plugin-ui/viewport/screenshot.tsx b/src/mol-plugin-ui/viewport/screenshot.tsx index 6127b348c2ec06d90aeef3c5dab0efddace55f4a..5bac5ff97ed628a49b288eab74f4b6aeec4aa869 100644 --- a/src/mol-plugin-ui/viewport/screenshot.tsx +++ b/src/mol-plugin-ui/viewport/screenshot.tsx @@ -9,10 +9,10 @@ import * as React from 'react'; import { ParamDefinition as PD } from '../../mol-util/param-definition'; import { ParameterControls } from '../controls/parameters'; import { PluginUIComponent } from '../base'; -import { Icon } from '../controls/icons'; import { debounceTime } from 'rxjs/operators'; import { Subject } from 'rxjs'; import { ViewportScreenshotHelper } from '../../mol-plugin/util/viewport-screenshot'; +import { Button } from '../controls/common'; interface ImageControlsState { showPreview: boolean @@ -125,8 +125,8 @@ export class DownloadScreenshotControls extends PluginUIComponent<{ close: () => <span>Right-click the image to Copy.</span> </div> <div className='msp-btn-row-group'> - <button className='msp-btn msp-btn-block msp-form-control' onClick={this.download} disabled={this.state.isDisabled}><Icon name='download' /> Download</button> - <button className='msp-btn msp-btn-block msp-form-control' onClick={this.openTab} disabled={this.state.isDisabled}><Icon name='export' /> Open in new Tab</button> + <Button icon='download' onClick={this.download} disabled={this.state.isDisabled}>Download</Button> + <Button icon='export' onClick={this.openTab} disabled={this.state.isDisabled}>Open in new Tab</Button> </div> <ParameterControls params={this.plugin.helpers.viewportScreenshot!.params} values={this.plugin.helpers.viewportScreenshot!.values} onChange={this.setProps} isDisabled={this.state.isDisabled} /> </div>