ソースを参照

mol-plugin-ui: use ActionMenu in SelectControl

David Sehnal 5 年 前
コミット
509e633a69

+ 21 - 13
src/mol-plugin-ui/controls/action-menu.tsx

@@ -45,7 +45,8 @@ export namespace ActionMenu {
         menu: ActionMenu,
         disabled?: boolean,
         items: ActionMenu.Spec,
-        header: string,
+        header?: string,
+        label?: string,
         current?: ActionMenu.Item,
         onSelect: (value: any) => void
     }
@@ -78,11 +79,17 @@ export namespace ActionMenu {
 
         hide = () => this.setState({ isSelected: false });
 
+        onClick = (e: React.MouseEvent<HTMLButtonElement>) => {
+            e.currentTarget.blur();
+            this.props.menu.toggle(this.props);
+        }
+
         render() {
             const props = this.props;
-            return <button onClick={() => props.menu.toggle(props)} 
+            const label = props.label || props.header;
+            return <button onClick={this.onClick} 
                 disabled={props.disabled} style={props.style} className={props.className}>
-                    {this.state.isSelected ? <b>{props.header}</b> : props.header}
+                    {this.state.isSelected ? <b>{label}</b> : label}
             </button>;
         }
     }
@@ -120,19 +127,19 @@ export namespace ActionMenu {
 
         render() {
             if (!this.state.isVisible || this.state.command.type !== 'toggle') return null;
-            return <div className='msp-action-menu-options'>
+            return <div className='msp-action-menu-options' style={{ marginTop: '1px' }}>
                 {this.state.command.header && <div className='msp-control-group-header' style={{ position: 'relative' }}>
                     <button className='msp-btn msp-btn-block' onClick={this.hide}>
                         <Icon name='off' style={{ position: 'absolute', right: '2px', top: 0 }} />
                         <b>{this.state.command.header}</b>
                     </button>
                 </div>}
-                <Section items={this.state.command.items} onSelect={this.onSelect} />
+                <Section menu={this.props.menu} items={this.state.command.items} onSelect={this.onSelect} current={this.state.command.current} />
             </div>
         }
     }
 
-    class Section extends React.PureComponent<{ header?: string, items: Spec, onSelect: OnSelect }, { isExpanded: boolean }> {
+    class Section extends React.PureComponent<{ menu: ActionMenu, header?: string, items: Spec, onSelect: OnSelect, current: Item | undefined  }, { isExpanded: boolean }> {
         state = { isExpanded: false }
 
         toggleExpanded = (e: React.MouseEvent<HTMLButtonElement>) => {
@@ -141,9 +148,9 @@ export namespace ActionMenu {
         }
 
         render() {
-            const { header, items, onSelect } = this.props;
+            const { header, items, onSelect, current, menu } = this.props;
             if (typeof items === 'string') return null;
-            if (isItem(items)) return <Action item={items} onSelect={onSelect} />
+            if (isItem(items)) return <Action menu={menu} item={items} onSelect={onSelect} current={current} />
             return <div>
                 {header && <div className='msp-control-group-header' style={{ marginTop: '1px' }}>
                     <button className='msp-btn msp-btn-block' onClick={this.toggleExpanded}>
@@ -154,19 +161,20 @@ export namespace ActionMenu {
                 <div className='msp-control-offset'>
                     {(!header || this.state.isExpanded) && items.map((x, i) => {
                         if (typeof x === 'string') return null;
-                        if (isItem(x)) return <Action key={i} item={x} onSelect={onSelect} />
-                        return <Section key={i} header={typeof x[0] === 'string' ? x[0] : void 0} items={x} onSelect={onSelect} />
+                        if (isItem(x)) return <Action menu={menu} key={i} item={x} onSelect={onSelect} current={current} />
+                        return <Section menu={menu} key={i} header={typeof x[0] === 'string' ? x[0] : void 0} items={x} onSelect={onSelect} current={current} />
                     })}
                 </div>
             </div>;
         }
     }
 
-    const Action: React.FC<{ item: Item, onSelect: OnSelect }> = ({ item, onSelect }) => {
+    const Action: React.FC<{ menu: ActionMenu, item: Item, onSelect: OnSelect, current: Item | undefined }> = ({ menu, item, onSelect, current }) => {
+        const isCurrent = current === item;
         return <div className='msp-control-row'>
-            <button onClick={() => onSelect(item)}>
+            <button onClick={isCurrent ? () => menu.hide() : () => onSelect(item)}>
                 {item.icon && <Icon name={item.icon} />}
-                {item.name}
+                {isCurrent ? <b>{item.name}</b> : item.name}
             </button>
         </div>;
     }

+ 27 - 11
src/mol-plugin-ui/controls/parameters.tsx

@@ -22,6 +22,7 @@ import { CombinedColorControl, ColorValueOption, ColorOptions } from './color';
 import { getPrecision } from '../../mol-util/number';
 import { ParamMapping } from '../../mol-util/param-mapping';
 import { PluginContext } from '../../mol-plugin/context';
+import { ActionMenu } from './action-menu';
 
 export interface ParameterControlsProps<P extends PD.Params = PD.Params> {
     params: P,
@@ -135,7 +136,8 @@ export abstract class SimpleParam<P extends PD.Any> extends React.PureComponent<
     }
 
     abstract renderControl(): JSX.Element;
-
+    renderAddOn(): JSX.Element | null { return null; }
+    
     private get className() {
         const className = ['msp-control-row'];
         if (this.props.param.shortLabel) className.push('msp-control-label-short')
@@ -143,6 +145,7 @@ export abstract class SimpleParam<P extends PD.Any> extends React.PureComponent<
         return className.join(' ')
     }
 
+
     toggleExpanded = () => this.setState({ isExpanded: !this.state.isExpanded });
 
     render() {
@@ -171,6 +174,7 @@ export abstract class SimpleParam<P extends PD.Any> extends React.PureComponent<
             {hasHelp && this.state.isExpanded && <div className='msp-control-offset'>
                 <ParamHelp legend={help.legend} description={help.description} />
             </div>}
+            {this.renderAddOn()}
         </>;
     }
 }
@@ -319,20 +323,32 @@ export class PureSelectControl extends  React.PureComponent<ParamProps<PD.Select
 }
 
 export class SelectControl extends SimpleParam<PD.Select<string | number>> {
-    onChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
-        if (typeof this.props.param.defaultValue === 'number') {
-            this.update(parseInt(e.target.value, 10));
-        } else {
-            this.update(e.target.value);
-        }
+    menu = new ActionMenu();
+    onSelect = (value: string) => {
+        this.update(value);
     }
 
     renderControl() {
         const isInvalid = this.props.value !== void 0 && !this.props.param.options.some(e => e[0] === this.props.value);
-        return <select value={this.props.value !== void 0 ? this.props.value : this.props.param.defaultValue} onChange={this.onChange} disabled={this.props.isDisabled}>
-            {isInvalid && <option key={this.props.value} value={this.props.value}>{`[Invalid] ${this.props.value}`}</option>}
-            {this.props.param.options.map(([value, label]) => <option key={value} value={value}>{label}</option>)}
-        </select>;
+        const items: ActionMenu.Item[] = [];
+        let current: ActionMenu.Item | undefined = void 0;
+        if (isInvalid) {
+            current = ActionMenu.Item(`[Invalid] ${this.props.value}`, this.props.value);
+            items.push(current);
+        }
+        for (const [value, label] of this.props.param.options) {
+            const item = ActionMenu.Item(label, value);
+            items.push(item);
+            if (value === this.props.value) current = item;
+        }
+
+        return <ActionMenu.Toggle menu={this.menu} disabled={this.props.isDisabled} 
+            onSelect={this.onSelect} items={items as ActionMenu.Spec} label={current?.name}
+            current={current} />;
+    }
+
+    renderAddOn() {
+        return <ActionMenu.Options menu={this.menu} />;
     }
 }
 

+ 1 - 1
src/mol-plugin-ui/structure/selection.tsx

@@ -210,7 +210,7 @@ export class StructureSelectionControls<P, S extends StructureSelectionControlsS
     actionMenu = new ActionMenu();
 
     controls = <div>
-        <div className='msp-control-row msp-button-row' style={{ marginBottom: '1px' }}>
+        <div className='msp-control-row msp-button-row'>
             <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} />