Browse Source

Merge branch 'master' into gl-lines

Alexander Rose 6 years ago
parent
commit
9a9bf3abc7

+ 75 - 105
src/mol-plugin/state/actions/basic.ts

@@ -21,79 +21,58 @@ import { ElementSymbolColorThemeParams } from 'mol-theme/color/element-symbol';
 // TODO: "structure parser provider"
 
 export { DownloadStructure }
-namespace DownloadStructure {
-    export type Source = PD.NamedParamUnion<ObtainStructureHelpers.ControlMap>
-    export interface Params {
-        source: Source
+type DownloadStructure = typeof DownloadStructure
+const DownloadStructure = StateAction.build({
+    from: PluginStateObject.Root,
+    display: { name: 'Download Structure', description: 'Load a structure from the provided source and create its default Assembly and visual.' },
+    params: {
+        source: PD.MappedStatic('bcif-static', {
+            'pdbe-updated': PD.Text('1cbs', { label: 'Id' }),
+            'rcsb': PD.Text('1tqn', { label: 'Id' }),
+            'bcif-static': PD.Text('1tqn', { label: 'Id' }),
+            'url': PD.Group({ url: PD.Text(''), isBinary: PD.Boolean(false) }, { isExpanded: true })
+        }, {
+            options: [
+                ['pdbe-updated', 'PDBe Updated'],
+                ['rcsb', 'RCSB'],
+                ['bcif-static', 'BinaryCIF (static PDBe Updated)'],
+                ['url', 'URL']
+            ]
+        })
     }
-}
-namespace ObtainStructureHelpers {
-    export const ControlMap = {
-        'pdbe-updated': PD.Text('1cbs', { label: 'Id' }),
-        'rcsb': PD.Text('1tqn', { label: 'Id' }),
-        'bcif-static': PD.Text('1tqn', { label: 'Id' }),
-        'url': PD.Group({ url: PD.Text(''), isBinary: PD.Boolean(false) }, { isExpanded: true })
+})(({ params, state }) => {
+    const b = state.build();
+    const src = params.source;
+    let url: Transformer.Params<Download>;
+
+    switch (src.name) {
+        case 'url':
+            url = src.params;
+            break;
+        case 'pdbe-updated':
+            url = { url: `https://www.ebi.ac.uk/pdbe/static/entry/${src.params.toLowerCase()}_updated.cif`, isBinary: false, label: `PDBe: ${src.params}` };
+            break;
+        case 'rcsb':
+            url = { url: `https://files.rcsb.org/download/${src.params.toUpperCase()}.cif`, isBinary: false, label: `RCSB: ${src.params}` };
+            break;
+        case 'bcif-static':
+            url = { url: `https://webchem.ncbr.muni.cz/ModelServer/static/bcif/${src.params.toLowerCase()}`, isBinary: true, label: `BinaryCIF: ${src.params}` };
+            break;
+        default: throw new Error(`${(src as any).name} not supported.`);
     }
-    export type ControlMap = typeof ControlMap
-    export const SourceOptions: [keyof ControlMap, string][] = [
-        ['pdbe-updated', 'PDBe Updated'],
-        ['rcsb', 'RCSB'],
-        ['bcif-static', 'BinaryCIF (static PDBe Updated)'],
-        ['url', 'URL']
-    ];
-
-    export function getControls(key: string) { return (ControlMap as any)[key]; }
-    export function getUrl(src: DownloadStructure.Source): Transformer.Params<Download> {
-        switch (src.name) {
-            case 'url': return src.params;
-            case 'pdbe-updated': return { url: `https://www.ebi.ac.uk/pdbe/static/entry/${src.params.toLowerCase()}_updated.cif`, isBinary: false, label: `PDBe: ${src.params}` };
-            case 'rcsb': return { url: `https://files.rcsb.org/download/${src.params.toUpperCase()}.cif`, isBinary: false, label: `RCSB: ${src.params}` };
-            case 'bcif-static': return { url: `https://webchem.ncbr.muni.cz/ModelServer/static/bcif/${src.params.toLowerCase()}`, isBinary: true, label: `BinaryCIF: ${src.params}` };
-            default: throw new Error(`${(src as any).name} not supported.`);
-        }
-    }
-}
-const DownloadStructure = StateAction.create<PluginStateObject.Root, void, DownloadStructure.Params>({
-    from: [PluginStateObject.Root],
-    display: {
-        name: 'Download Structure',
-        description: 'Load a structure from PDBe and create its default Assembly and visual'
-    },
-    params: () => ({ source: PD.Mapped('bcif-static', ObtainStructureHelpers.SourceOptions, ObtainStructureHelpers.getControls) }),
-    apply({ params, state }) {
-        const b = state.build();
-
-        // const query = MolScriptBuilder.struct.generator.atomGroups({
-        //     // 'atom-test': MolScriptBuilder.core.rel.eq([
-        //     //     MolScriptBuilder.struct.atomProperty.macromolecular.label_comp_id(),
-        //     //     MolScriptBuilder.es('C')
-        //     // ]),
-        //     'residue-test': MolScriptBuilder.core.rel.eq([
-        //         MolScriptBuilder.struct.atomProperty.macromolecular.label_comp_id(),
-        //         'ALA'
-        //     ])
-        // });
-
-        const url = ObtainStructureHelpers.getUrl(params.source);
 
-        const data = b.toRoot().apply(StateTransforms.Data.Download, url);
-        return state.update(createStructureTree(data));
-    }
+    const data = b.toRoot().apply(StateTransforms.Data.Download, url);
+    return state.update(createStructureTree(data));
 });
 
 export const OpenStructure = StateAction.build({
+    display: { name: 'Open Structure', description: 'Load a structure from file and create its default Assembly and visual' },
     from: PluginStateObject.Root,
     params: { file: PD.File({ accept: '.cif,.bcif' }) }
-})({
-    display: {
-        name: 'Open Structure',
-        description: 'Load a structure from file and create its default Assembly and visual'
-    },
-    apply({ params, state }) {
-        const b = state.build();
-        const data = b.toRoot().apply(StateTransforms.Data.ReadFile, { file: params.file, isBinary: /\.bcif$/i.test(params.file.name) });
-        return state.update(createStructureTree(data));
-    }
+})(({ params, state }) => {
+    const b = state.build();
+    const data = b.toRoot().apply(StateTransforms.Data.ReadFile, { file: params.file, isBinary: /\.bcif$/i.test(params.file.name) });
+    return state.update(createStructureTree(data));
 });
 
 function createStructureTree(b: StateTreeBuilder.To<PluginStateObject.Data.Binary | PluginStateObject.Data.String>): StateTree {
@@ -128,57 +107,48 @@ function complexRepresentation(root: StateTreeBuilder.To<PluginStateObject.Molec
             sizeTheme: { name: 'uniform', params: PD.getDefaultValues(UniformSizeThemeParams) },
         })
     root.apply(StateTransforms.Model.StructureComplexElement, { type: 'spheres' });
-        // TODO: create spheres visual
+    // TODO: create spheres visual
 }
 
 export const CreateComplexRepresentation = StateAction.build({
+    display: { name: 'Create Complex', description: 'Split the structure into Sequence/Water/Ligands/... ' },
     from: PluginStateObject.Molecule.Structure
-})({
-    display: {
-        name: 'Create Complex',
-        description: 'Split the structure into Sequence/Water/Ligands/... '
-    },
-    apply({ ref, state }) {
-        const root = state.build().to(ref);
-        complexRepresentation(root);
-        return state.update(root.getTree());
-    }
+})(({ ref, state }) => {
+    const root = state.build().to(ref);
+    complexRepresentation(root);
+    return state.update(root.getTree());
 });
 
 export const UpdateTrajectory = StateAction.build({
-    params: () => ({
+    display: { name: 'Update Trajectory' },
+    params: {
         action: PD.Select<'advance' | 'reset'>('advance', [['advance', 'Advance'], ['reset', 'Reset']]),
         by: PD.makeOptional(PD.Numeric(1, { min: -1, max: 1, step: 1 }))
-    })
-})({
-    display: {
-        name: 'Update Trajectory'
-    },
-    apply({ params, state }) {
-        const models = state.select(q => q.rootsOfType(PluginStateObject.Molecule.Model)
-            .filter(c => c.transform.transformer === StateTransforms.Model.ModelFromTrajectory));
+    }
+})(({ params, state }) => {
+    const models = state.select(q => q.rootsOfType(PluginStateObject.Molecule.Model)
+        .filter(c => c.transform.transformer === StateTransforms.Model.ModelFromTrajectory));
 
-        const update = state.build();
+    const update = state.build();
 
-        if (params.action === 'reset') {
-            for (const m of models) {
-                update.to(m.transform.ref).update(StateTransforms.Model.ModelFromTrajectory,
-                    () => ({ modelIndex: 0 }));
-            }
-        } else {
-            for (const m of models) {
-                const parent = StateSelection.findAncestorOfType(state.tree, state.cells, m.transform.ref, [PluginStateObject.Molecule.Trajectory]);
-                if (!parent || !parent.obj) continue;
-                const traj = parent.obj as PluginStateObject.Molecule.Trajectory;
-                update.to(m.transform.ref).update(StateTransforms.Model.ModelFromTrajectory,
-                    old => {
-                        let modelIndex = (old.modelIndex + params.by!) % traj.data.length;
-                        if (modelIndex < 0) modelIndex += traj.data.length;
-                        return { modelIndex };
-                    });
-            }
+    if (params.action === 'reset') {
+        for (const m of models) {
+            update.to(m.transform.ref).update(StateTransforms.Model.ModelFromTrajectory,
+                () => ({ modelIndex: 0 }));
+        }
+    } else {
+        for (const m of models) {
+            const parent = StateSelection.findAncestorOfType(state.tree, state.cells, m.transform.ref, [PluginStateObject.Molecule.Trajectory]);
+            if (!parent || !parent.obj) continue;
+            const traj = parent.obj as PluginStateObject.Molecule.Trajectory;
+            update.to(m.transform.ref).update(StateTransforms.Model.ModelFromTrajectory,
+                old => {
+                    let modelIndex = (old.modelIndex + params.by!) % traj.data.length;
+                    if (modelIndex < 0) modelIndex += traj.data.length;
+                    return { modelIndex };
+                });
         }
-
-        return state.update(update);
     }
+
+    return state.update(update);
 });

+ 4 - 13
src/mol-plugin/state/transforms/data.ts

@@ -17,6 +17,7 @@ export { Download }
 type Download = typeof Download
 const Download = PluginStateTransform.BuiltIn({
     name: 'download',
+    display: { name: 'Download', description: 'Download string or binary data from the specified URL' },
     from: [SO.Root],
     to: [SO.Data.String, SO.Data.Binary],
     params: {
@@ -25,10 +26,6 @@ const Download = PluginStateTransform.BuiltIn({
         isBinary: PD.makeOptional(PD.Boolean(false, { description: 'If true, download data as binary (string otherwise)' }))
     }
 })({
-    display: {
-        name: 'Download',
-        description: 'Download string or binary data from the specified URL'
-    },
     apply({ params: p }, globalCtx: PluginContext) {
         return Task.create('Download', async ctx => {
             const data = await globalCtx.fetch(p.url, p.isBinary ? 'binary' : 'string').runInContext(ctx);
@@ -51,18 +48,15 @@ export { ReadFile }
 type ReadFile = typeof ReadFile
 const ReadFile = PluginStateTransform.BuiltIn({
     name: 'read-file',
+    display: { name: 'Read File', description: 'Read string or binary data from the specified file' },
     from: SO.Root,
     to: [SO.Data.String, SO.Data.Binary],
     params: {
         file: PD.File(),
         label: PD.makeOptional(PD.Text('')),
         isBinary: PD.makeOptional(PD.Boolean(false, { description: 'If true, open file as as binary (string otherwise)' }))
-    },
+    }
 })({
-    display: {
-        name: 'Read File',
-        description: 'Read string or binary data from the specified file'
-    },
     apply({ params: p }) {
         return Task.create('Open File', async ctx => {
             const data = await readFromFile(p.file, p.isBinary ? 'binary' : 'string').runInContext(ctx);
@@ -85,13 +79,10 @@ export { ParseCif }
 type ParseCif = typeof ParseCif
 const ParseCif = PluginStateTransform.BuiltIn({
     name: 'parse-cif',
+    display: { name: 'Parse CIF', description: 'Parse CIF from String or Binary data' },
     from: [SO.Data.String, SO.Data.Binary],
     to: SO.Format.Cif
 })({
-    display: {
-        name: 'Parse CIF',
-        description: 'Parse CIF from String or Binary data'
-    },
     apply({ a }) {
         return Task.create('Parse CIF', async ctx => {
             const parsed = await (SO.Data.String.is(a) ? CIF.parse(a.data) : CIF.parseBinary(a.data)).runInContext(ctx);

+ 11 - 29
src/mol-plugin/state/transforms/model.ts

@@ -19,6 +19,7 @@ export { TrajectoryFromMmCif }
 type TrajectoryFromMmCif = typeof TrajectoryFromMmCif
 const TrajectoryFromMmCif = PluginStateTransform.BuiltIn({
     name: 'trajectory-from-mmcif',
+    display: { name: 'Trajectory from mmCIF', description: 'Identify and create all separate models in the specified CIF data block' },
     from: SO.Format.Cif,
     to: SO.Molecule.Trajectory,
     params(a) {
@@ -26,12 +27,8 @@ const TrajectoryFromMmCif = PluginStateTransform.BuiltIn({
         return {
             blockHeader: PD.makeOptional(PD.Select(blocks[0] && blocks[0].header, blocks.map(b => [b.header, b.header] as [string, string]), { description: 'Header of the block to parse' }))
         };
-    },
+    }
 })({
-    display: {
-        name: 'Models from mmCIF',
-        description: 'Identify and create all separate models in the specified CIF data block'
-    },
     isApplicable: a => a.data.blocks.length > 0,
     apply({ a, params }) {
         return Task.create('Parse mmCIF', async ctx => {
@@ -51,14 +48,11 @@ const plus1 = (v: number) => v + 1, minus1 = (v: number) => v - 1;
 type ModelFromTrajectory = typeof ModelFromTrajectory
 const ModelFromTrajectory = PluginStateTransform.BuiltIn({
     name: 'model-from-trajectory',
+    display: { name: 'Model from Trajectory', description: 'Create a molecular structure from the specified model.' },
     from: SO.Molecule.Trajectory,
     to: SO.Molecule.Model,
     params: a => ({ modelIndex: PD.Converted(plus1, minus1, PD.Numeric(1, { min: 1, max: a.data.length, step: 1 }, { description: 'Model Index' })) })
 })({
-    display: {
-        name: 'Model from Trajectory',
-        description: 'Create a molecular structure from the specified model.'
-    },
     isApplicable: a => a.data.length > 0,
     apply({ a, params }) {
         if (params.modelIndex < 0 || params.modelIndex >= a.data.length) throw new Error(`Invalid modelIndex ${params.modelIndex}`);
@@ -72,13 +66,10 @@ export { StructureFromModel }
 type StructureFromModel = typeof StructureFromModel
 const StructureFromModel = PluginStateTransform.BuiltIn({
     name: 'structure-from-model',
+    display: { name: 'Structure from Model', description: 'Create a molecular structure from the specified model.' },
     from: SO.Molecule.Model,
-    to: SO.Molecule.Structure,
+    to: SO.Molecule.Structure
 })({
-    display: {
-        name: 'Structure from Model',
-        description: 'Create a molecular structure from the specified model.'
-    },
     apply({ a }) {
         let s = Structure.ofModel(a.data);
         const label = { label: a.data.label, description: s.elementCount === 1 ? '1 element' : `${s.elementCount} elements` };
@@ -94,6 +85,7 @@ export { StructureAssemblyFromModel }
 type StructureAssemblyFromModel = typeof StructureAssemblyFromModel
 const StructureAssemblyFromModel = PluginStateTransform.BuiltIn({
     name: 'structure-assembly-from-model',
+    display: { name: 'Structure Assembly', description: 'Create a molecular structure assembly.' },
     from: SO.Molecule.Model,
     to: SO.Molecule.Structure,
     params(a) {
@@ -102,10 +94,6 @@ const StructureAssemblyFromModel = PluginStateTransform.BuiltIn({
         return { id: PD.makeOptional(PD.Select(ids.length ? ids[0][0] : '', ids, { label: 'Asm Id', description: 'Assembly Id' })) };
     }
 })({
-    display: {
-        name: 'Structure Assembly',
-        description: 'Create a molecular structure assembly.'
-    },
     apply({ a, params }, plugin: PluginContext) {
         return Task.create('Build Assembly', async ctx => {
             let id = (params.id || '').trim();
@@ -132,17 +120,14 @@ export { StructureSelection }
 type StructureSelection = typeof StructureSelection
 const StructureSelection = PluginStateTransform.BuiltIn({
     name: 'structure-selection',
+    display: { name: 'Structure Selection', description: 'Create a molecular structure from the specified model.' },
     from: SO.Molecule.Structure,
     to: SO.Molecule.Structure,
-    params: () => ({
+    params: {
         query: PD.Value<Expression>(MolScriptBuilder.struct.generator.all, { isHidden: true }),
         label: PD.makeOptional(PD.Text('', { isHidden: true }))
-    })
+    }
 })({
-    display: {
-        name: 'Structure Selection',
-        description: 'Create a molecular structure from the specified model.'
-    },
     apply({ a, params }) {
         // TODO: use cache, add "update"
         const compiled = compile<Sel>(params.query);
@@ -158,14 +143,11 @@ namespace StructureComplexElement { export type Types = 'atomic-sequence' | 'wat
 type StructureComplexElement = typeof StructureComplexElement
 const StructureComplexElement = PluginStateTransform.BuiltIn({
     name: 'structure-complex-element',
+    display: { name: 'Complex Element', description: 'Create a molecular structure from the specified model.' },
     from: SO.Molecule.Structure,
     to: SO.Molecule.Structure,
-    params: () => ({ type: PD.Text<StructureComplexElement.Types>('atomic-sequence', { isHidden: true }) }),
+    params: { type: PD.Text<StructureComplexElement.Types>('atomic-sequence', { isHidden: true }) }
 })({
-    display: {
-        name: 'Complex Element',
-        description: 'Create a molecular structure from the specified model.'
-    },
     apply({ a, params }) {
         // TODO: update function.
 

+ 1 - 1
src/mol-plugin/state/transforms/representation.ts

@@ -17,6 +17,7 @@ export { StructureRepresentation3D }
 type StructureRepresentation3D = typeof StructureRepresentation3D
 const StructureRepresentation3D = PluginStateTransform.BuiltIn({
     name: 'structure-representation-3d',
+    display: '3D Representation',
     from: SO.Molecule.Structure,
     to: SO.Molecule.Representation3D,
     params: (a, ctx: PluginContext) => ({
@@ -38,7 +39,6 @@ const StructureRepresentation3D = PluginStateTransform.BuiltIn({
         )
     })
 })({
-    display: { name: '3D Representation' },
     canAutoUpdate({ oldParams, newParams }) {
         // TODO: allow for small molecules
         return oldParams.type.name === newParams.type.name;

+ 1 - 1
src/mol-plugin/ui/state-tree.tsx

@@ -162,7 +162,7 @@ class StateTreeNodeLabel extends PluginComponent<{ nodeRef: string, state: State
 
         let label: any;
         if (cell.status !== 'ok' || !cell.obj) {
-            const name = (n.transformer.definition.display && n.transformer.definition.display.name) || n.transformer.definition.name;
+            const name = n.transformer.definition.display.name;
             const title = `${cell.errorText}`
             label = <><b>{cell.status}</b> <a title={title} href='#' onClick={this.setCurrent}>{name}</a>: <i>{cell.errorText}</i></>;
         } else {

+ 0 - 1
src/mol-plugin/ui/state/apply-action.tsx

@@ -42,7 +42,6 @@ class ApplyActionContol extends TransformContolBase<ApplyActionContol.Props, App
     }
     getInfo() { return this._getInfo(this.props.nodeRef, this.props.state.transforms.get(this.props.nodeRef).version); }
     getHeader() { return this.props.action.definition.display; }
-    getHeaderFallback() { return this.props.action.id; }
     canApply() { return !this.state.error && !this.state.busy; }
     canAutoApply() { return false; }
     applyText() { return 'Apply'; }

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

@@ -99,7 +99,6 @@ abstract class TransformContolBase<P, S extends TransformContolBase.ControlState
     abstract applyAction(): Promise<void>;
     abstract getInfo(): StateTransformParameters.Props['info'];
     abstract getHeader(): Transformer.Definition['display'];
-    abstract getHeaderFallback(): string;
     abstract canApply(): boolean;
     abstract canAutoApply(newParams: any): boolean;
     abstract applyText(): string;
@@ -171,7 +170,7 @@ abstract class TransformContolBase<P, S extends TransformContolBase.ControlState
 
         return <div className='msp-transform-wrapper'>
             <div className='msp-transform-header'>
-                <button className='msp-btn msp-btn-block' onClick={this.toggleExpanded}>{(display && display.name) || this.getHeaderFallback()}</button>
+                <button className='msp-btn msp-btn-block' onClick={this.toggleExpanded}>{display.name}</button>
                 {!this.state.isCollapsed && <button className='msp-btn msp-btn-link msp-transform-default-params' onClick={this.setDefault} disabled={this.state.busy} style={{ float: 'right'}} title='Set default params'>↻</button>}
             </div>
             {!this.state.isCollapsed && <>

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

@@ -29,7 +29,6 @@ class UpdateTransformContol extends TransformContolBase<UpdateTransformContol.Pr
     applyAction() { return this.plugin.updateTransform(this.props.state, this.props.transform.ref, this.state.params); }
     getInfo() { return this._getInfo(this.props.transform); }
     getHeader() { return this.props.transform.transformer.definition.display; }
-    getHeaderFallback() { return this.props.transform.transformer.definition.name; }
     canApply() { return !this.state.error && !this.state.busy && !this.state.isInitial; }
     applyText() { return this.canApply() ? 'Update' : 'Nothing to Update'; }
     isUpdate() { return true; }

+ 14 - 7
src/mol-state/action.ts

@@ -39,12 +39,10 @@ namespace StateAction {
     }
 
     export interface DefinitionBase<A extends StateObject = StateObject, T = any, P extends {} = {}> {
-        readonly display?: { readonly name: string, readonly description?: string },
-
         /**
          * Apply an action that modifies the State specified in Params.
          */
-        apply(params: ApplyParams<A, P>, globalCtx: unknown): T | Task<T>,
+        run(params: ApplyParams<A, P>, globalCtx: unknown): T | Task<T>,
 
         /** Test if the transform can be applied to a given node */
         isApplicable?(a: A, globalCtx: unknown): boolean
@@ -52,6 +50,7 @@ namespace StateAction {
 
     export interface Definition<A extends StateObject = StateObject, T = any, P extends {} = {}> extends DefinitionBase<A, T, P> {
         readonly from: StateObject.Ctor[],
+        readonly display: { readonly name: string, readonly description?: string },
         params?(a: A, globalCtx: unknown): { [K in keyof P]: PD.Any }
     }
 
@@ -70,7 +69,7 @@ namespace StateAction {
             from: def.from,
             display: def.display,
             params: def.params as Transformer.Definition<Transformer.From<T>, any, Transformer.Params<T>>['params'],
-            apply({ cell, state, params }) {
+            run({ cell, state, params }) {
                 const tree = state.build().to(cell.transform.ref).apply(transformer, params);
                 return state.update(tree);
             }
@@ -80,7 +79,8 @@ namespace StateAction {
     export namespace Builder {
         export interface Type<A extends StateObject.Ctor, P extends { }> {
             from?: A | A[],
-            params?: PD.For<P> | ((a: StateObject.From<A>, globalCtx: any) => PD.For<P>)
+            params?: PD.For<P> | ((a: StateObject.From<A>, globalCtx: any) => PD.For<P>),
+            display?: string | { name: string, description?: string }
         }
 
         export interface Root {
@@ -88,7 +88,7 @@ namespace StateAction {
         }
 
         export interface Define<A extends StateObject, P> {
-            <T>(def: DefinitionBase<A, T, P>): StateAction<A, T, P>
+            <T>(def: DefinitionBase<A, T, P> | DefinitionBase<A, T, P>['run']): StateAction<A, T, P>,
         }
 
         function root(info: Type<any, any>): Define<any, any> {
@@ -96,12 +96,19 @@ namespace StateAction {
                 from: info.from instanceof Array
                     ? info.from
                     : !!info.from ? [info.from] : [],
+                display: typeof info.display === 'string'
+                    ? { name: info.display }
+                    : !!info.display
+                    ? info.display
+                    : { name: 'Unnamed State Action' },
                 params: typeof info.params === 'object'
                     ? () => info.params as any
                     : !!info.params
                     ? info.params as any
                     : void 0,
-                ...def
+                ...(typeof def === 'function'
+                    ? { run: def }
+                    : def)
             });
         }
 

+ 1 - 1
src/mol-state/state.ts

@@ -104,7 +104,7 @@ class State {
             if (!cell) throw new Error(`'${ref}' does not exist.`);
             if (cell.status !== 'ok') throw new Error(`Action cannot be applied to a cell with status '${cell.status}'`);
 
-            return runTask(action.definition.apply({ ref, cell, a: cell.obj!, params, state: this }, this.globalContext), ctx);
+            return runTask(action.definition.run({ ref, cell, a: cell.obj!, params, state: this }, this.globalContext), ctx);
         });
     }
 

+ 10 - 3
src/mol-state/transformer.ts

@@ -9,6 +9,7 @@ import { StateObject } from './object';
 import { Transform } from './transform';
 import { ParamDefinition as PD } from 'mol-util/param-definition';
 import { StateAction } from './action';
+import { capitalize } from 'mol-util/string';
 
 export interface Transformer<A extends StateObject = StateObject, B extends StateObject = StateObject, P extends {} = {}> {
     apply(parent: Transform.Ref, params?: P, props?: Partial<Transform.Options>): Transform<A, B, P>,
@@ -57,8 +58,6 @@ export namespace Transformer {
     // export type ParamsDefinition<A extends StateObject = StateObject, P = any> = (a: A, globalCtx: unknown) => { [K in keyof P]: PD.Any }
 
     export interface DefinitionBase<A extends StateObject = StateObject, B extends StateObject = StateObject, P extends {} = {}> {
-        readonly display?: { readonly name: string, readonly description?: string },
-
         /**
          * Apply the actual transformation. It must be pure (i.e. with no side effects).
          * Returns a task that produces the result of the result directly.
@@ -89,6 +88,7 @@ export namespace Transformer {
         readonly name: string,
         readonly from: StateObject.Ctor[],
         readonly to: StateObject.Ctor[],
+        readonly display: { readonly name: string, readonly description?: string },
         params?(a: A, globalCtx: unknown): { [K in keyof P]: PD.Any },
     }
 
@@ -151,7 +151,8 @@ export namespace Transformer {
             name: string,
             from: A | A[],
             to: B | B[],
-            params?: PD.For<P> | ((a: StateObject.From<A>, globalCtx: any) => PD.For<P>)
+            params?: PD.For<P> | ((a: StateObject.From<A>, globalCtx: any) => PD.For<P>),
+            display?: string | { name: string, description?: string }
         }
 
         export interface Root {
@@ -167,6 +168,11 @@ export namespace Transformer {
                 name: info.name,
                 from: info.from instanceof Array ? info.from : [info.from],
                 to: info.to instanceof Array ? info.to : [info.to],
+                display: typeof info.display === 'string'
+                    ? { name: info.display }
+                    : !!info.display
+                    ? info.display
+                    : { name: capitalize(info.name.replace(/[-]/g, ' ')) },
                 params: typeof info.params === 'object'
                     ? () => info.params as any
                     : !!info.params
@@ -189,6 +195,7 @@ export namespace Transformer {
         name: 'root',
         from: [],
         to: [],
+        display: { name: 'Root' },
         apply() { throw new Error('should never be applied'); },
         update() { return UpdateResult.Unchanged; }
     })

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

@@ -151,17 +151,29 @@ export namespace ParamDefinition {
 
     export interface NamedParams<T = any, K = string> { name: K, params: T }
     export type NamedParamUnion<P extends Params, K = keyof P> = K extends any ? NamedParams<P[K]['defaultValue'], K> : never
-    export interface Mapped<T> extends Base<NamedParams<T>> {
+    export interface Mapped<T extends NamedParams<any, any>> extends Base<T> {
         type: 'mapped',
         select: Select<string>,
         map(name: string): Any
     }
-    export function Mapped<T>(defaultKey: string, names: [string, string][], map: Mapped<T>['map'], info?: Info): Mapped<T> {
-        return setInfo<Mapped<T>>({
+    export function Mapped<T>(defaultKey: string, names: [string, string][], map: (name: string) => Any, info?: Info): Mapped<NamedParams<T>> {
+        return setInfo<Mapped<NamedParams<T>>>({
             type: 'mapped',
             defaultValue: { name: defaultKey, params: map(defaultKey).defaultValue as any },
             select: Select<string>(defaultKey, names, info),
-            map }, info);
+            map
+        }, info);
+    }
+    export function MappedStatic<C extends Params>(defaultKey: keyof C, map: C, info?: Info & { options?: [keyof C, string][] }): Mapped<NamedParamUnion<C>> {
+        const options: [string, string][] = info && info.options
+            ? info.options as [string, string][]
+            : Object.keys(map).map(k => [k, k]) as [string, string][];
+        return setInfo<Mapped<NamedParamUnion<C>>>({
+            type: 'mapped',
+            defaultValue: { name: defaultKey, params: map[defaultKey].defaultValue } as any,
+            select: Select<string>(defaultKey as string, options, info),
+            map: key => map[key]
+        }, info);
     }
 
     export interface Converted<T, C> extends Base<T> {