Ver código fonte

ParamDefinition categories (wip)

David Sehnal 5 anos atrás
pai
commit
bda04cbdc7

+ 2 - 2
src/mol-geo/geometry/base.ts

@@ -35,8 +35,8 @@ export const VisualQualityOptions = PD.arrayToOptions(VisualQualityNames)
 
 export namespace BaseGeometry {
     export const Params = {
-        alpha: PD.Numeric(1, { min: 0, max: 1, step: 0.01 }, { label: 'Opacity' }),
-        quality: PD.Select<VisualQuality>('auto', VisualQualityOptions),
+        alpha: PD.Numeric(1, { min: 0, max: 1, step: 0.01 }, { label: 'Opacity', category: PD.Categories.Simple }),
+        quality: PD.Select<VisualQuality>('auto', VisualQualityOptions, PD.SimpleCategory),
     }
     export type Params = typeof Params
 

+ 12 - 6
src/mol-plugin-state/transforms/representation.ts

@@ -151,14 +151,16 @@ const StructureRepresentation3D = PluginStateTransform.BuiltIn({
                     const p = themeCtx.colorThemeRegistry.get(name)
                     const ct = p.factory({}, params)
                     return { description: ct.description, legend: ct.legend }
-                }
+                },
+                category: PD.Categories.Simple
             }
 
             return {
                 type: PD.Mapped<any>(
                     registry.default.name,
                     registry.types,
-                    name => PD.Group<any>(registry.get(name).getParams(themeCtx, Structure.Empty))),
+                    name => PD.Group<any>(registry.get(name).getParams(themeCtx, Structure.Empty)),
+                    PD.SimpleCategory),
                 colorTheme: PD.Mapped<any>(
                     type.defaultColorTheme.name,
                     themeCtx.colorThemeRegistry.types,
@@ -168,7 +170,8 @@ const StructureRepresentation3D = PluginStateTransform.BuiltIn({
                 sizeTheme: PD.Mapped<any>(
                     type.defaultSizeTheme.name,
                     themeCtx.sizeThemeRegistry.types,
-                    name => PD.Group<any>(themeCtx.sizeThemeRegistry.get(name).getParams({ structure: Structure.Empty }))
+                    name => PD.Group<any>(themeCtx.sizeThemeRegistry.get(name).getParams({ structure: Structure.Empty })),
+                    PD.SimpleCategory
                 )
             }
         }
@@ -180,14 +183,16 @@ const StructureRepresentation3D = PluginStateTransform.BuiltIn({
                 const p = themeCtx.colorThemeRegistry.get(name)
                 const ct = p.factory(dataCtx, params)
                 return { description: ct.description, legend: ct.legend }
-            }
+            },
+            category: PD.Categories.Simple
         }
 
         return ({
             type: PD.Mapped<any>(
                 registry.default.name,
                 registry.getApplicableTypes(a.data),
-                name => PD.Group<any>(registry.get(name).getParams(themeCtx, a.data))),
+                name => PD.Group<any>(registry.get(name).getParams(themeCtx, a.data)),
+                PD.SimpleCategory),
             colorTheme: PD.Mapped<any>(
                 type.defaultColorTheme.name,
                 themeCtx.colorThemeRegistry.getApplicableTypes(dataCtx),
@@ -197,7 +202,8 @@ const StructureRepresentation3D = PluginStateTransform.BuiltIn({
             sizeTheme: PD.Mapped<any>(
                 type.defaultSizeTheme.name,
                 themeCtx.sizeThemeRegistry.types,
-                name => PD.Group<any>(themeCtx.sizeThemeRegistry.get(name).getParams(dataCtx))
+                name => PD.Group<any>(themeCtx.sizeThemeRegistry.get(name).getParams(dataCtx)),
+                PD.SimpleCategory
             )
         })
     }

+ 33 - 19
src/mol-plugin-ui/controls/parameters.tsx

@@ -24,11 +24,15 @@ import { ParamMapping } from '../../mol-util/param-mapping';
 import { PluginContext } from '../../mol-plugin/context';
 import { ActionMenu } from './action-menu';
 
+export type ParameterControlsCategoryFilter = string | null | (string | null)[]
+
 export interface ParameterControlsProps<P extends PD.Params = PD.Params> {
     params: P,
     values: any,
     onChange: ParamsOnChange<PD.Values<P>>,
     isDisabled?: boolean,
+    /** null <=> "support" PD.categories === undefined */
+    categoryFilter?: ParameterControlsCategoryFilter
     onEnter?: () => void
 }
 
@@ -38,7 +42,8 @@ export class ParameterControls<P extends PD.Params> extends React.PureComponent<
     render() {
         const params = this.props.params;
         const values = this.props.values;
-        const keys = Object.keys(params);
+        const filter = this.props.categoryFilter;
+        const keys = filterParamKeys(params, filter);
         if (keys.length === 0 || values === undefined) return null;
         return <>
             {keys.map(key => {
@@ -46,7 +51,7 @@ export class ParameterControls<P extends PD.Params> extends React.PureComponent<
                 if (param.isHidden) return null;
                 const Control = controlFor(param);
                 if (!Control) return null;
-                return <Control param={param} key={key} onChange={this.onChange} onEnter={this.props.onEnter} isDisabled={this.props.isDisabled} name={key} value={values[key]} />
+                return <Control param={param} key={key} onChange={this.onChange} onEnter={this.props.onEnter} isDisabled={this.props.isDisabled} name={key} value={values[key]} categoryFilter={filter} />
             })}
         </>;
     }
@@ -71,6 +76,10 @@ export class ParameterMappingControl<S, T> extends PluginUIComponent<{ mapping:
     }
 }
 
+function filterParamKeys(params: PD.Params, filter: ParameterControlsCategoryFilter | undefined) {
+    return filter === void 0 ? Object.keys(params) : Object.keys(params).filter(key => PD.hasCategory(params[key], filter));
+}
+
 function controlFor(param: PD.Any): ParamControl | undefined {
     switch (param.type) {
         case 'value': return void 0;
@@ -122,6 +131,7 @@ export interface ParamProps<P extends PD.Base<any> = PD.Base<any>> {
     value: P['defaultValue'],
     param: P,
     isDisabled?: boolean,
+    categoryFilter?: ParameterControlsCategoryFilter
     onChange: ParamOnChange,
     onEnter?: () => void
 }
@@ -418,7 +428,7 @@ export class IntervalControl extends React.PureComponent<ParamProps<PD.Interval>
                 </div>
             </div>
             <div className='msp-control-offset' style={{ display: this.state.isExpanded ? 'block' : 'none' }}>
-                <ParameterControls params={this.components} values={v} onChange={this.componentChange} onEnter={this.props.onEnter} />
+                <ParameterControls params={this.components} values={v} onChange={this.componentChange} onEnter={this.props.onEnter} categoryFilter={this.props.categoryFilter} />
             </div>
         </>;
     }
@@ -543,7 +553,7 @@ export class Vec3Control extends React.PureComponent<ParamProps<PD.Vec3>, { isEx
                 </div>
             </div>
             <div className='msp-control-offset' style={{ display: this.state.isExpanded ? 'block' : 'none' }}>
-                <ParameterControls params={this.components} values={v} onChange={this.componentChange} onEnter={this.props.onEnter} />
+                <ParameterControls params={this.components} values={v} onChange={this.componentChange} onEnter={this.props.onEnter} categoryFilter={this.props.categoryFilter} />
             </div>
         </>;
     }
@@ -658,11 +668,11 @@ export class GroupControl extends React.PureComponent<ParamProps<PD.Group<any>>
         const params = this.props.param.params;
 
         // Do not show if there are no params.
-        if (Object.keys(params).length === 0) return null;
+        if (filterParamKeys(params, this.props.categoryFilter).length === 0) return null;
 
         const label = this.props.param.label || camelCaseToWords(this.props.name);
 
-        const controls = <ParameterControls params={params} onChange={this.onChangeParam} values={this.props.value} onEnter={this.props.onEnter} isDisabled={this.props.isDisabled} />;
+        const controls = <ParameterControls params={params} onChange={this.onChangeParam} values={this.props.value} onEnter={this.props.onEnter} isDisabled={this.props.isDisabled} categoryFilter={this.props.categoryFilter} />;
 
         if (this.props.inMapped) {
             return <div className='msp-control-offset'>{controls}</div>;
@@ -738,22 +748,26 @@ export class MappedControl extends React.PureComponent<ParamProps<PD.Mapped<any>
             return Select;
         }
 
-        if (param.type === 'group' && !param.isFlat && Object.keys(param.params).length > 0) {
-            return <div className='msp-mapped-parameter-group'>
-                {Select}
-                <IconButton icon='log' onClick={this.toggleExpanded} toggleState={this.state.isExpanded} title={`${label} Properties`} />
-                {this.state.isExpanded && <GroupControl inMapped param={param} value={value.params} name={`${label} Properties`} onChange={this.onChangeParam} onEnter={this.props.onEnter} isDisabled={this.props.isDisabled} />}
-            </div>
+        if (param.type === 'group' && !param.isFlat) {
+            if (filterParamKeys(param.params, this.props.categoryFilter).length > 0) {
+                return <div className='msp-mapped-parameter-group'>
+                    {Select}
+                    <IconButton icon='log' onClick={this.toggleExpanded} toggleState={this.state.isExpanded} title={`${label} Properties`} />
+                    {this.state.isExpanded && <GroupControl inMapped param={param} value={value.params} name={`${label} Properties`} onChange={this.onChangeParam} onEnter={this.props.onEnter} isDisabled={this.props.isDisabled} categoryFilter={this.props.categoryFilter} />}
+                </div>
+            }
+
+            return Select;
         }
 
         return <>
             {Select}
-            <Mapped param={param} value={value.params} name={`${label} Properties`} onChange={this.onChangeParam} onEnter={this.props.onEnter} isDisabled={this.props.isDisabled} />
+            <Mapped param={param} value={value.params} name={`${label} Properties`} onChange={this.onChangeParam} onEnter={this.props.onEnter} isDisabled={this.props.isDisabled} categoryFilter={this.props.categoryFilter} />
         </>
     }
 }
 
-class ObjectListEditor extends React.PureComponent<{ params: PD.Params, value: object, isUpdate?: boolean, apply: (value: any) => void, isDisabled?: boolean }, { params: PD.Params, value: object, current: object }> {
+class ObjectListEditor extends React.PureComponent<{ params: PD.Params, value: object, isUpdate?: boolean, apply: (value: any) => void, isDisabled?: boolean, categoryFilter?: ParameterControlsCategoryFilter }, { params: PD.Params, value: object, current: object }> {
     state = { params: {}, value: void 0 as any, current: void 0 as any };
 
     onChangeParam: ParamOnChange = e => {
@@ -775,7 +789,7 @@ class ObjectListEditor extends React.PureComponent<{ params: PD.Params, value: o
 
     render() {
         return <>
-            <ParameterControls params={this.props.params} onChange={this.onChangeParam} values={this.state.current} onEnter={this.apply} isDisabled={this.props.isDisabled} />
+            <ParameterControls params={this.props.params} onChange={this.onChangeParam} values={this.state.current} onEnter={this.apply} isDisabled={this.props.isDisabled} categoryFilter={this.props.categoryFilter} />
             <button className={`msp-btn msp-btn-block msp-form-control msp-control-top-offset`} onClick={this.apply} disabled={this.props.isDisabled}>
                 {this.props.isUpdate ? 'Update' : 'Add'}
             </button>
@@ -783,7 +797,7 @@ class ObjectListEditor extends React.PureComponent<{ params: PD.Params, value: o
     }
 }
 
-class ObjectListItem extends React.PureComponent<{ param: PD.ObjectList, value: object, index: number, actions: ObjectListControl['actions'], isDisabled?: boolean }, { isExpanded: boolean }> {
+class ObjectListItem extends React.PureComponent<{ param: PD.ObjectList, value: object, index: number, actions: ObjectListControl['actions'], isDisabled?: boolean, categoryFilter?: ParameterControlsCategoryFilter }, { isExpanded: boolean }> {
     state = { isExpanded: false };
 
     update = (v: object) => {
@@ -832,7 +846,7 @@ class ObjectListItem extends React.PureComponent<{ param: PD.ObjectList, value:
                 </div>
             </div>
             {this.state.isExpanded && <div className='msp-control-offset'>
-                <ObjectListEditor params={this.props.param.element} apply={this.update} value={this.props.value} isUpdate isDisabled={this.props.isDisabled} />
+                <ObjectListEditor params={this.props.param.element} apply={this.update} value={this.props.value} isUpdate isDisabled={this.props.isDisabled} categoryFilter={this.props.categoryFilter} />
             </div>}
         </>;
     }
@@ -896,9 +910,9 @@ export class ObjectListControl extends React.PureComponent<ParamProps<PD.ObjectL
             </div>
 
             {this.state.isExpanded && <div className='msp-control-offset'>
-                {this.props.value.map((v, i) => <ObjectListItem key={i} param={this.props.param} value={v} index={i} actions={this.actions} />)}
+                {this.props.value.map((v, i) => <ObjectListItem key={i} param={this.props.param} value={v} index={i} actions={this.actions} categoryFilter={this.props.categoryFilter} />)}
                 <ControlGroup header='New Item'>
-                    <ObjectListEditor params={this.props.param.element} apply={this.add} value={this.props.param.ctor()} isDisabled={this.props.isDisabled} />
+                    <ObjectListEditor params={this.props.param.element} apply={this.add} value={this.props.param.ctor()} isDisabled={this.props.isDisabled} categoryFilter={this.props.categoryFilter} />
                 </ControlGroup>
             </div>}
         </>;

+ 10 - 3
src/mol-plugin-ui/state/common.tsx

@@ -31,7 +31,7 @@ class StateTransformParameters extends PurePluginUIComponent<StateTransformParam
     };
 
     render() {
-        return <ParameterControls params={this.props.info.params} values={this.props.params} onChange={this.onChange} onEnter={this.props.events.onEnter} isDisabled={this.props.isDisabled} />;
+        return <ParameterControls params={this.props.info.params} values={this.props.params} onChange={this.onChange} onEnter={this.props.events.onEnter} isDisabled={this.props.isDisabled} categoryFilter={this.props.simpleOnly ? PD.Categories.Simple : void 0} />;
     }
 }
 
@@ -50,7 +50,8 @@ namespace StateTransformParameters {
         params: any,
         isDisabled?: boolean,
         a?: StateObject,
-        b?: StateObject
+        b?: StateObject,
+        simpleOnly?: boolean
     }
 
     export type Class = React.ComponentClass<Props>
@@ -95,6 +96,7 @@ namespace TransformControlBase {
         error?: string,
         busy: boolean,
         isInitial: boolean,
+        simpleOnly?: boolean,
         isCollapsed?: boolean
     }
 }
@@ -170,6 +172,10 @@ abstract class TransformControlBase<P, S extends TransformControlBase.ComponentS
         this.setState({ isCollapsed: !this.state.isCollapsed });
     }
 
+    toggleSimple = () => {
+        this.setState({ simpleOnly: !this.state.simpleOnly });
+    }
+
     render() {
         const info = this.getInfo();
         const isEmpty = info.isEmpty && this.isUpdate();
@@ -197,7 +203,7 @@ abstract class TransformControlBase<P, S extends TransformControlBase.ComponentS
                 </button>
             </div>}
             {!isEmpty && !this.state.isCollapsed && <>
-                <ParamEditor info={info} a={a} b={b} events={this.events} params={this.state.params} isDisabled={this.state.busy} />
+                <ParamEditor info={info} a={a} b={b} events={this.events} params={this.state.params} isDisabled={this.state.busy} simpleOnly={this.state.simpleOnly} />
 
                 <div className='msp-transform-apply-wrap'>
                     <button className='msp-btn msp-btn-block msp-transform-default-params' onClick={this.setDefault} disabled={this.state.busy} title='Set default params'><Icon name='cw' /></button>
@@ -210,6 +216,7 @@ abstract class TransformControlBase<P, S extends TransformControlBase.ComponentS
                             {this.applyText()}
                         </button>
                     </div>
+                    {this.isUpdate() && <button className='msp-btn msp-btn-block msp-transform-default-params' style={{ left: '33px' }}  onClick={this.toggleSimple} disabled={this.state.busy} title='Show/hide simple params'><Icon name='log' /></button>}
                 </div>
             </>}
         </div>

+ 2 - 1
src/mol-plugin-ui/state/update-transform.tsx

@@ -89,7 +89,8 @@ class UpdateTransformControl extends TransformControlBase<UpdateTransformControl
             transform: props.transform,
             params: (cell.params && cell.params.values) || { },
             isInitial: true,
-            error: void 0
+            error: void 0,
+            simpleOnly: state.simpleOnly
         };
         return newState;
     }

+ 33 - 4
src/mol-util/param-definition.ts

@@ -14,19 +14,29 @@ import { Legend } from './legend';
 import { stringToWords } from './string';
 
 export namespace ParamDefinition {
-    export interface Info {
+    interface InfoBase {
         label?: string,
         description?: string,
         legend?: Legend,
         fieldLabels?: { [name: string]: string },
         isHidden?: boolean,
         shortLabel?: boolean,
-        twoColumns?: boolean,
+        twoColumns?: boolean
 
         help?: (value: any) => { description?: string, legend?: Legend }
     }
 
-    function setInfo<T extends Info>(param: T, info?: Info): T {
+    export enum Categories {
+        Simple = 'Simple'
+    }
+
+    export const SimpleCategory = { category: [Categories.Simple] };
+
+    export interface Info extends InfoBase {
+        category?: string | string[]
+    }
+
+    function setInfo<T extends Base<any>>(param: T, info?: Info): T {
         if (!info) return param;
         if (info.label) param.label = info.label;
         if (info.description) param.description = info.description;
@@ -35,12 +45,16 @@ export namespace ParamDefinition {
         if (info.isHidden) param.isHidden = info.isHidden;
         if (info.shortLabel) param.shortLabel = info.shortLabel;
         if (info.twoColumns) param.twoColumns = info.twoColumns;
+        if (info.category) {
+            param.categories = typeof info.category === 'string' ? [info.category] : info.category.length > 0 ? info.category : void 0;
+        }
 
         if (info.help) param.help = info.help;
         return param;
     }
 
-    export interface Base<T> extends Info {
+    export interface Base<T> extends InfoBase {
+        categories?: ReadonlyArray<string>
         isOptional?: boolean,
         defaultValue: T
     }
@@ -373,6 +387,21 @@ export namespace ParamDefinition {
         return false;
     }
 
+    const _isNullish = (x: any) => !x;
+    export function hasCategory(param: Any, filter?: string | null | (string | null)[]) {
+        if (!filter || filter.length === 0) return filter === null || filter?.length === 0 ? !param.categories : false;
+        if (!param.categories) return !filter || (typeof filter !== 'string' && filter.some(_isNullish));
+        if (typeof filter === 'string') return param.categories.indexOf(filter) >= 0;
+        if (filter.length === 1 && filter[0]) return param.categories.indexOf(filter[0]) >= 0;
+
+        for (const c of param.categories) {
+            for (const d of filter) {
+                if (c === d) return true;
+            }
+        }
+        return false;
+    }
+
     /**
      * Map an object to a list of [K, string][] to be used as options, stringToWords for key used by default (or identity of null).
      *