Skip to content
Snippets Groups Projects
Commit 17fac2b8 authored by David Sehnal's avatar David Sehnal
Browse files

mol-plugin-ui: ActionMenu refactoring

parent c543c4e1
No related branches found
No related tags found
No related merge requests found
......@@ -6,50 +6,128 @@
import * as React from 'react'
import { Icon } from './common';
import { Observable, Subscription } from 'rxjs';
import { Subscription, BehaviorSubject, Observable } from 'rxjs';
export class ActionMenu {
private _command: BehaviorSubject<ActionMenu.Command>;
get commands(): Observable<ActionMenu.Command> { return this._command; }
hide() {
this._command.next(HideCmd)
}
toggle(params: { items: ActionMenu.Spec, header?: string, current?: ActionMenu.Item, onSelect: (value: any) => void }) {
this._command.next({ type: 'toggle', ...params });
}
constructor(defaultCommand?: ActionMenu.Command) {
this._command = new BehaviorSubject<ActionMenu.Command>(defaultCommand || { type: 'hide' });
}
}
const HideCmd: ActionMenu.Command = { type: 'hide' };
export namespace ActionMenu {
export class Options extends React.PureComponent<{ toggle: Observable<OptionsParams | undefined>, hide?: Observable<any> }, { options: OptionsParams | undefined, isVisible: boolean }> {
private subs: Subscription[] = [];
export type Command =
| { type: 'toggle', items: Spec, header?: string, current?: Item, onSelect: (value: any) => void }
| { type: 'hide' }
function isToggleOff(a: Command, b: Command) {
if (a.type === 'hide' || b.type === 'hide') return false;
return a.onSelect === b.onSelect && a.items === b.items;
}
state = { isVisible: false, options: void 0 as OptionsParams | undefined }
export type ToggleProps = {
style?: React.HTMLAttributes<HTMLButtonElement>,
className?: string,
menu: ActionMenu,
disabled?: boolean,
items: ActionMenu.Spec,
header: string,
current?: ActionMenu.Item,
onSelect: (value: any) => void
}
export class Toggle extends React.PureComponent<ToggleProps, { isSelected: boolean }> {
private sub: Subscription | undefined = void 0;
state = { isSelected: false };
componentDidMount() {
this.subs.push(this.props.toggle.subscribe(options => {
if (options && this.state.options?.items === options.items && this.state.options?.onSelect === options.onSelect) {
this.setState({ isVisible: !this.state.isVisible});
} else {
this.setState({ isVisible: !!options, options: options })
this.sub = this.props.menu.commands.subscribe(command => {
if (command.type === 'hide') {
this.hide();
} else if (command.type === 'toggle') {
const cmd = this.props;
if (command.items === cmd.items && command.onSelect === cmd.onSelect) {
this.setState({ isSelected: !this.state.isSelected });
} else {
this.hide();
}
}
}));
});
}
componentWillUnmount() {
if (!this.sub) return;
this.sub.unsubscribe();
this.sub = void 0;
}
if (this.props.hide) {
this.subs.push(this.props.hide.subscribe(() => this.hide()));
}
hide = () => this.setState({ isSelected: false });
render() {
const props = this.props;
return <button onClick={() => props.menu.toggle(props)}
disabled={props.disabled} style={props.style} className={props.className}>
{this.state.isSelected ? <b>{props.header}</b> : props.header}
</button>;
}
}
export class Options extends React.PureComponent<{ menu: ActionMenu }, { command: Command, isVisible: boolean }> {
private sub: Subscription | undefined = void 0;
state = { isVisible: false, command: HideCmd };
componentDidMount() {
this.sub = this.props.menu.commands.subscribe(command => {
if (command.type === 'hide' || isToggleOff(command, this.state.command)) {
this.setState({ isVisible: false, command: HideCmd });
} else {
this.setState({ isVisible: true, command })
}
});
}
componentWillUnmount() {
if (!this.subs) return;
for (const s of this.subs) s.unsubscribe();
this.subs = [];
if (!this.sub) return;
this.sub.unsubscribe();
this.sub = void 0;
}
onSelect: OnSelect = item => {
this.setState({ isVisible: false, options: void 0 });
this.state.options?.onSelect(item.value);
const cmd = this.state.command;
this.hide();
if (cmd.type === 'toggle') cmd.onSelect(item.value);
}
hide = () => this.setState({ isVisible: false, options: void 0 });
hide = () => {
this.props.menu.hide();
}
render() {
if (!this.state.isVisible || !this.state.options) return null;
if (!this.state.isVisible || this.state.command.type !== 'toggle') return null;
return <div className='msp-action-menu-options'>
{this.state.options.header && <div className='msp-control-group-header'>
{this.state.command.header && <div className='msp-control-group-header' style={{ position: 'relative' }}>
<button className='msp-btn msp-btn-block' onClick={this.hide}>
{this.state.options.header}
<Icon name='off' style={{ position: 'absolute', right: '2px', top: 0 }} />
<b>{this.state.command.header}</b>
</button>
</div>}
<Section items={this.state.options!.items} onSelect={this.onSelect} />
<Section items={this.state.command.items} onSelect={this.onSelect} />
</div>
}
}
......@@ -67,7 +145,7 @@ export namespace ActionMenu {
if (typeof items === 'string') return null;
if (isItem(items)) return <Action item={items} onSelect={onSelect} />
return <div>
{header && <div className='msp-control-group-header'>
{header && <div className='msp-control-group-header' style={{ marginTop: '1px' }}>
<button className='msp-btn msp-btn-block' onClick={this.toggleExpanded}>
<span className={`msp-icon msp-icon-${this.state.isExpanded ? 'collapse' : 'expand'}`} />
{header}
......
......@@ -15,7 +15,6 @@ import { ParameterControls } from '../controls/parameters';
import { stripTags, stringToWords } from '../../mol-util/string';
import { StructureElement, StructureQuery, StructureSelection } from '../../mol-model/structure';
import { ActionMenu } from '../controls/action-menu';
import { Subject } from 'rxjs';
import { compile } from '../../mol-script/runtime/query/compiler';
import { MolScriptBuilder as MS } from '../../mol-script/language/builder';
......@@ -126,7 +125,10 @@ export class StructureSelectionControls<P, S extends StructureSelectionControlsS
this.forceUpdate()
});
this.subscribe(this.plugin.state.dataState.events.isUpdating, v => this.setState({ isDisabled: v }))
this.subscribe(this.plugin.state.dataState.events.isUpdating, v => {
this.actionMenu.hide();
this.setState({ isDisabled: v })
})
}
get stats() {
......@@ -205,15 +207,15 @@ export class StructureSelectionControls<P, S extends StructureSelectionControlsS
queries = DefaultQueries
actionMenu = new Subject<ActionMenu.OptionsParams | undefined>();
actionMenu = new ActionMenu();
controls = <div>
<div className='msp-control-row msp-button-row' style={{ marginBottom: '1px' }}>
<button onClick={() => this.actionMenu.next({ items: this.queries, header: 'Select', onSelect: this.add }) } disabled={this.state.isDisabled}>Select</button>
<button onClick={() => this.actionMenu.next({ items: this.queries, header: 'Deselect', onSelect: this.remove }) } disabled={this.state.isDisabled}>Deselect</button>
<button onClick={() => this.actionMenu.next({ items: this.queries, header: 'Only', onSelect: this.only }) } disabled={this.state.isDisabled}>Only</button>
<ActionMenu.Toggle menu={this.actionMenu} items={this.queries} header='Select' onSelect={this.add} disabled={this.state.isDisabled} />
<ActionMenu.Toggle menu={this.actionMenu} items={this.queries} header='Deselect' onSelect={this.remove} disabled={this.state.isDisabled} />
<ActionMenu.Toggle menu={this.actionMenu} items={this.queries} header='Only' onSelect={this.only} disabled={this.state.isDisabled} />
</div>
<ActionMenu.Options toggle={this.actionMenu} hide={this.plugin.state.dataState.events.isUpdating} />
<ActionMenu.Options menu={this.actionMenu} />
</div>
defaultState() {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment