From e1efe21897b39de3e7ec37e2b17917ab8ca03dd5 Mon Sep 17 00:00:00 2001 From: David Sehnal <david.sehnal@gmail.com> Date: Fri, 16 Nov 2018 00:02:56 +0100 Subject: [PATCH] mol-state: param definition --- src/mol-plugin/behavior/behavior.ts | 3 +- .../behavior/dynamic/representation.ts | 6 ++- src/mol-plugin/state/actions/basic.ts | 15 +++----- src/mol-plugin/state/transforms/data.ts | 17 +++------ src/mol-plugin/state/transforms/model.ts | 38 +++++++++---------- src/mol-plugin/state/transforms/visuals.ts | 29 +++++--------- src/mol-plugin/ui/state/apply-action.tsx | 6 +-- src/mol-plugin/ui/state/common.tsx | 19 ++++------ src/mol-state/action.ts | 4 +- src/mol-state/transformer.ts | 16 +++----- src/mol-state/tree/transient.ts | 10 ++--- src/mol-util/param-definition.ts | 6 ++- 12 files changed, 71 insertions(+), 98 deletions(-) diff --git a/src/mol-plugin/behavior/behavior.ts b/src/mol-plugin/behavior/behavior.ts index f15d4fcfd..dd23f7144 100644 --- a/src/mol-plugin/behavior/behavior.ts +++ b/src/mol-plugin/behavior/behavior.ts @@ -10,6 +10,7 @@ import { Task } from 'mol-task'; import { PluginContext } from 'mol-plugin/context'; import { PluginCommand } from '../command'; import { Observable } from 'rxjs'; +import { ParamDefinition } from 'mol-util/param-definition'; export { PluginBehavior } @@ -36,7 +37,7 @@ namespace PluginBehavior { group: string, description?: string }, - params?: Transformer.Definition<Root, Behavior, P>['params'], + params(a: Root, globalCtx: PluginContext): { [K in keyof P]: ParamDefinition.Any } } export function create<P>(params: CreateParams<P>) { diff --git a/src/mol-plugin/behavior/dynamic/representation.ts b/src/mol-plugin/behavior/dynamic/representation.ts index d9d211670..2c23b793c 100644 --- a/src/mol-plugin/behavior/dynamic/representation.ts +++ b/src/mol-plugin/behavior/dynamic/representation.ts @@ -25,7 +25,8 @@ export const HighlightLoci = PluginBehavior.create({ }); } }, - display: { name: 'Highlight Loci on Canvas', group: 'Data' } + display: { name: 'Highlight Loci on Canvas', group: 'Data' }, + params: () => ({}) }); export const SelectLoci = PluginBehavior.create({ @@ -38,5 +39,6 @@ export const SelectLoci = PluginBehavior.create({ }); } }, - display: { name: 'Select Loci on Canvas', group: 'Data' } + display: { name: 'Select Loci on Canvas', group: 'Data' }, + params: () => ({}) }); \ No newline at end of file diff --git a/src/mol-plugin/state/actions/basic.ts b/src/mol-plugin/state/actions/basic.ts index 7ccbf3c40..65914ce4c 100644 --- a/src/mol-plugin/state/actions/basic.ts +++ b/src/mol-plugin/state/actions/basic.ts @@ -17,13 +17,7 @@ export const CreateStructureFromPDBe = StateAction.create<PluginStateObject.Root name: 'Entry from PDBe', description: 'Download a structure from PDBe and create its default Assembly and visual' }, - params: { - default: () => ({ id: '1grm' }), - definition: () => ({ - id: PD.Text('1grm', { label: 'PDB id' }), - }), - // validate: p => !p.id || !p.id.trim() ? [['Enter id.', 'id']] : void 0 - }, + params: () => ({ id: PD.Text('1grm', { label: 'PDB id' }) }), apply({ params, state }) { const url = `http://www.ebi.ac.uk/pdbe/static/entry/${params.id.toLowerCase()}_updated.cif`; const b = state.build(); @@ -63,9 +57,10 @@ export const UpdateTrajectory = StateAction.create<PluginStateObject.Root, void, display: { name: 'Update Trajectory' }, - params: { - default: () => ({ action: 'reset', by: 1 }) - }, + params: () => ({ + action: PD.Select('advance', [['advance', 'Advance'], ['reset', 'Reset']]), + by: PD.Numeric(1, { min: -1, max: 1, step: 1 }, { isOptional: true }) + }), apply({ params, state }) { const models = state.select(q => q.rootsOfType(PluginStateObject.Molecule.Model).filter(c => c.transform.transformer === StateTransforms.Model.CreateModelFromTrajectory)); diff --git a/src/mol-plugin/state/transforms/data.ts b/src/mol-plugin/state/transforms/data.ts index 78499dcd9..f70d6a970 100644 --- a/src/mol-plugin/state/transforms/data.ts +++ b/src/mol-plugin/state/transforms/data.ts @@ -22,17 +22,11 @@ const Download = PluginStateTransform.Create<SO.Root, SO.Data.String | SO.Data.B }, from: [SO.Root], to: [SO.Data.String, SO.Data.Binary], - params: { - default: () => ({ - url: 'https://www.ebi.ac.uk/pdbe/static/entry/1cbs_updated.cif' - }), - definition: () => ({ - url: PD.Text('', { description: 'Resource URL. Must be the same domain or support CORS.' }), - label: PD.Text(''), - isBinary: PD.Boolean(false, { description: 'If true, download data as binary (string otherwise)' }) - }), - // validate: p => !p.url || !p.url.trim() ? [['Enter url.', 'url']] : void 0 - }, + params: () => ({ + url: PD.Text('https://www.ebi.ac.uk/pdbe/static/entry/1cbs_updated.cif', { description: 'Resource URL. Must be the same domain or support CORS.' }), + label: PD.Text('', { isOptional: true }), + isBinary: PD.Boolean(false, { description: 'If true, download data as binary (string otherwise)', isOptional: true }) + }), apply({ params: p }, globalCtx: PluginContext) { return Task.create('Download', async ctx => { // TODO: track progress @@ -62,6 +56,7 @@ const ParseCif = PluginStateTransform.Create<SO.Data.String | SO.Data.Binary, SO }, from: [SO.Data.String, SO.Data.Binary], to: [SO.Data.Cif], + params: () => ({}), 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); diff --git a/src/mol-plugin/state/transforms/model.ts b/src/mol-plugin/state/transforms/model.ts index 4c914e8ff..35930a399 100644 --- a/src/mol-plugin/state/transforms/model.ts +++ b/src/mol-plugin/state/transforms/model.ts @@ -12,6 +12,7 @@ import { ParamDefinition as PD } from 'mol-util/param-definition'; import Expression from 'mol-script/language/expression'; import { compile } from 'mol-script/runtime/query/compiler'; import { Mat4 } from 'mol-math/linear-algebra'; +import { MolScriptBuilder } from 'mol-script/language/builder'; export { ParseTrajectoryFromMmCif } namespace ParseTrajectoryFromMmCif { export interface Params { blockHeader?: string } } @@ -23,16 +24,14 @@ const ParseTrajectoryFromMmCif = PluginStateTransform.Create<SO.Data.Cif, SO.Mol }, from: [SO.Data.Cif], to: [SO.Molecule.Trajectory], - params: { - default: a => ({ blockHeader: a.data.blocks[0].header }), - definition(a) { - const { blocks } = a.data; - if (blocks.length === 0) return {}; - return { - blockHeader: PD.Select(blocks[0].header, blocks.map(b => [b.header, b.header] as [string, string]), { description: 'Header of the block to parse' }) - }; - } + params(a) { + const { blocks } = a.data; + if (blocks.length === 0) return { }; + return { + blockHeader: PD.Select(blocks[0].header, blocks.map(b => [b.header, b.header] as [string, string]), { description: 'Header of the block to parse' }) + }; }, + isApplicable: a => a.data.blocks.length > 0, apply({ a, params }) { return Task.create('Parse mmCIF', async ctx => { const header = params.blockHeader || a.data.blocks[0].header; @@ -57,10 +56,7 @@ const CreateModelFromTrajectory = PluginStateTransform.Create<SO.Molecule.Trajec }, from: [SO.Molecule.Trajectory], to: [SO.Molecule.Model], - params: { - default: () => ({ modelIndex: 0 }), - definition: a => ({ modelIndex: PD.Converted(plus1, minus1, PD.Numeric(1, { min: 1, max: a.data.length, step: 1 }, { description: 'Model Index' })) }) - }, + params: a => ({ modelIndex: PD.Converted(plus1, minus1, PD.Numeric(1, { min: 1, max: a.data.length, step: 1 }, { description: 'Model Index' })) }), 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}`); @@ -80,6 +76,7 @@ const CreateStructure = PluginStateTransform.Create<SO.Molecule.Model, SO.Molecu }, from: [SO.Molecule.Model], to: [SO.Molecule.Structure], + params: () => ({}), apply({ a, params }) { let s = Structure.ofModel(a.data); if (params.transform3d) s = Structure.transform(s, params.transform3d); @@ -102,13 +99,10 @@ const CreateStructureAssembly = PluginStateTransform.Create<SO.Molecule.Model, S }, from: [SO.Molecule.Model], to: [SO.Molecule.Structure], - params: { - default: () => ({ id: void 0 }), - definition(a) { - const model = a.data; - const ids = model.symmetry.assemblies.map(a => [a.id, a.id] as [string, string]); - return { id: PD.Select(ids.length ? ids[0][0] : '', ids, { label: 'Asm Id', description: 'Assembly Id' }) }; - } + params(a) { + const model = a.data; + const ids = model.symmetry.assemblies.map(a => [a.id, a.id] as [string, string]); + return { id: PD.Select(ids.length ? ids[0][0] : '', ids, { label: 'Asm Id', description: 'Assembly Id' }) }; }, apply({ a, params }) { return Task.create('Build Assembly', async ctx => { @@ -136,6 +130,10 @@ const CreateStructureSelection = PluginStateTransform.Create<SO.Molecule.Structu }, from: [SO.Molecule.Structure], to: [SO.Molecule.Structure], + params: () => ({ + query: PD.Value<Expression>(MolScriptBuilder.struct.generator.all), + label: PD.Text('', { isOptional: true }) + }), apply({ a, params }) { // TODO: use cache, add "update" const compiled = compile<StructureSelection>(params.query); diff --git a/src/mol-plugin/state/transforms/visuals.ts b/src/mol-plugin/state/transforms/visuals.ts index b0238b017..1290a8d39 100644 --- a/src/mol-plugin/state/transforms/visuals.ts +++ b/src/mol-plugin/state/transforms/visuals.ts @@ -25,25 +25,16 @@ const CreateStructureRepresentation = PluginStateTransform.Create<SO.Molecule.St display: { name: 'Create 3D Representation' }, from: [SO.Molecule.Structure], to: [SO.Molecule.Representation3D], - params: { - default: (a, ctx: PluginContext) => ({ - type: { - name: ctx.structureReprensentation.registry.default.name, - params: ctx.structureReprensentation.registry.default.provider.defaultValues - } - }), - definition: (a, ctx: PluginContext) => ({ - type: PD.Mapped( - ctx.structureReprensentation.registry.default.name, - ctx.structureReprensentation.registry.types, - name => PD.Group( - ctx.structureReprensentation.registry.get(name).getParams(ctx.structureReprensentation.themeCtx, a.data), - { label: 'Type Parameters' } - ), - { label: 'Type' } - ) - }) - }, + params: (a, ctx: PluginContext) => ({ + type: PD.Mapped( + ctx.structureReprensentation.registry.default.name, + ctx.structureReprensentation.registry.types, + name => PD.Group<any>( + ctx.structureReprensentation.registry.get(name).getParams(ctx.structureReprensentation.themeCtx, a.data), + { label: 'Type Parameters' } + ), + { label: 'Type' }) + }), apply({ a, params }, plugin: PluginContext) { return Task.create('Structure Representation', async ctx => { const provider = plugin.structureReprensentation.registry.get(params.type.name) diff --git a/src/mol-plugin/ui/state/apply-action.tsx b/src/mol-plugin/ui/state/apply-action.tsx index 1af3b9071..ffa8768d5 100644 --- a/src/mol-plugin/ui/state/apply-action.tsx +++ b/src/mol-plugin/ui/state/apply-action.tsx @@ -10,6 +10,7 @@ import { State, Transform } from 'mol-state'; import { StateAction } from 'mol-state/action'; import { memoizeOne } from 'mol-util/memoize'; import { StateTransformParameters, TransformContolBase } from './common'; +import { ParamDefinition as PD } from 'mol-util/param-definition'; export { ApplyActionContol }; @@ -55,13 +56,12 @@ class ApplyActionContol extends TransformContolBase<ApplyActionContol.Props, App if (version === state.version) return null; const source = props.state.cells.get(props.nodeRef)!.obj!; - const definition = props.action.definition.params || { }; - const initialValues = definition.default ? definition.default(source, props.plugin) : {}; + const params = PD.getDefaultValues(props.action.definition.params(source, props.plugin)); const newState: Partial<ApplyActionContol.ComponentState> = { ref: props.nodeRef, version, - params: initialValues, + params, isInitial: true, error: void 0 }; diff --git a/src/mol-plugin/ui/state/common.tsx b/src/mol-plugin/ui/state/common.tsx index b5ebc0fb5..5ef1e7144 100644 --- a/src/mol-plugin/ui/state/common.tsx +++ b/src/mol-plugin/ui/state/common.tsx @@ -17,9 +17,7 @@ export { StateTransformParameters, TransformContolBase }; class StateTransformParameters extends PurePluginComponent<StateTransformParameters.Props> { getDefinition() { - const controls = this.props.info.definition.definition; - if (!controls) return { }; - return controls!(this.props.info.source, this.plugin) + return this.props.info.createDefinition(this.props.info.source, this.plugin) } validate(params: any) { @@ -45,7 +43,7 @@ class StateTransformParameters extends PurePluginComponent<StateTransformParamet namespace StateTransformParameters { export interface Props { info: { - definition: Transformer.ParamsDefinition, + createDefinition: Transformer.ParamsDefinition, params: PD.Params, initialValues: any, source: StateObject, @@ -63,12 +61,11 @@ namespace StateTransformParameters { export function infoFromAction(plugin: PluginContext, state: State, action: StateAction, nodeRef: Transform.Ref): Props['info'] { const source = state.cells.get(nodeRef)!.obj!; - const definition = action.definition.params || { }; - const initialValues = definition.default ? definition.default(source, plugin) : {}; - const params = definition.definition ? definition.definition(source, plugin) : {}; + const params = action.definition.params(source, plugin); + const initialValues = PD.getDefaultValues(params); return { source, - definition: action.definition.params || { }, + createDefinition: action.definition.params, initialValues, params, isEmpty: Object.keys(params).length === 0 @@ -78,11 +75,11 @@ namespace StateTransformParameters { export function infoFromTransform(plugin: PluginContext, state: State, transform: Transform): Props['info'] { const cell = state.cells.get(transform.ref)!; const source: StateObjectCell | undefined = (cell.sourceRef && state.cells.get(cell.sourceRef)!) || void 0; - const definition = transform.transformer.definition.params || { }; - const params = definition.definition ? definition.definition((source && source.obj) as any, plugin) : {}; + const createDefinition = transform.transformer.definition.params; + const params = createDefinition((source && source.obj) as any, plugin); return { source: (source && source.obj) as any, - definition, + createDefinition, initialValues: transform.params, params, isEmpty: Object.keys(params).length === 0 diff --git a/src/mol-state/action.ts b/src/mol-state/action.ts index e230f4e07..321a7092b 100644 --- a/src/mol-state/action.ts +++ b/src/mol-state/action.ts @@ -46,7 +46,7 @@ namespace StateAction { */ apply(params: ApplyParams<A, P>, globalCtx: unknown): T | Task<T>, - readonly params?: Transformer.ParamsDefinition<A, P> + params(a: A, globalCtx: unknown): { [K in keyof P]: PD.Any }, /** Test if the transform can be applied to a given node */ isApplicable?(a: A, globalCtx: unknown): boolean @@ -66,7 +66,7 @@ namespace StateAction { return create<Transformer.From<T>, void, Transformer.Params<T>>({ from: def.from, display: def.display, - params: def.params as Transformer<Transformer.From<T>, any, Transformer.Params<T>>['definition']['params'], + params: def.params as Transformer.ParamsDefinition<Transformer.From<T>, Transformer.Params<T>>, apply({ cell, state, params }) { const tree = state.build().to(cell.transform.ref).apply(transformer, params); return state.update(tree); diff --git a/src/mol-state/transformer.ts b/src/mol-state/transformer.ts index 0a68047ef..c6ce1ce72 100644 --- a/src/mol-state/transformer.ts +++ b/src/mol-state/transformer.ts @@ -23,7 +23,6 @@ export namespace Transformer { export type Params<T extends Transformer<any, any, any>> = T extends Transformer<any, any, infer P> ? P : unknown; export type From<T extends Transformer<any, any, any>> = T extends Transformer<infer A, any, any> ? A : unknown; export type To<T extends Transformer<any, any, any>> = T extends Transformer<any, infer B, any> ? B : unknown; - export type ControlsFor<Props> = { [P in keyof Props]?: PD.Any } export function is(obj: any): obj is Transformer { return !!obj && typeof (obj as Transformer).toAction === 'function' && typeof (obj as Transformer).apply === 'function'; @@ -47,14 +46,10 @@ export namespace Transformer { export enum UpdateResult { Unchanged, Updated, Recreate } - export interface ParamsDefinition<A extends StateObject = StateObject, P = any> { - /** Check the parameters and return a list of errors if the are not valid. */ - default?(a: A, globalCtx: unknown): P, - /** Specify default control descriptors for the parameters */ - definition?(a: A, globalCtx: unknown): { [K in keyof P]?: PD.Any }, - /** Optional custom parameter equality. Use shallow structural equal by default. */ - areEqual?(oldParams: P, newParams: P): boolean - } + export const EmptyParams = () => ({}); + + /** Specify default control descriptors for the parameters */ + export type ParamsDefinition<A extends StateObject = StateObject, P = any> = (a: A, globalCtx: unknown) => { [K in keyof P]: PD.Any } export interface Definition<A extends StateObject = StateObject, B extends StateObject = StateObject, P extends {} = {}> { readonly name: string, @@ -75,7 +70,7 @@ export namespace Transformer { */ update?(params: UpdateParams<A, B, P>, globalCtx: unknown): Task<UpdateResult> | UpdateResult, - readonly params?: ParamsDefinition<A, P>, + params(a: A, globalCtx: unknown): { [K in keyof P]: PD.Any }, /** Test if the transform can be applied to a given node */ isApplicable?(a: A, globalCtx: unknown): boolean, @@ -141,6 +136,7 @@ export namespace Transformer { name: 'root', from: [], to: [], + params: EmptyParams, apply() { throw new Error('should never be applied'); }, update() { return UpdateResult.Unchanged; } }) diff --git a/src/mol-state/tree/transient.ts b/src/mol-state/tree/transient.ts index 279ed6fcf..9646dab8f 100644 --- a/src/mol-state/tree/transient.ts +++ b/src/mol-state/tree/transient.ts @@ -138,13 +138,9 @@ class TransientTree implements StateTree { ensurePresent(this.transforms, ref); const transform = this.transforms.get(ref)!; - const def = transform.transformer.definition; - if (def.params && def.params.areEqual) { - if (def.params.areEqual(transform.params, params)) return false; - } else { - if (shallowEqual(transform.params, params)) { - return false; - } + // TODO: should this be here? + if (shallowEqual(transform.params, params)) { + return false; } if (!this.changedNodes) { diff --git a/src/mol-util/param-definition.ts b/src/mol-util/param-definition.ts index e53915ce0..0a8fbe577 100644 --- a/src/mol-util/param-definition.ts +++ b/src/mol-util/param-definition.ts @@ -12,14 +12,16 @@ import { camelCaseToWords } from './string'; export namespace ParamDefinition { export interface Info { - label?: string - description?: string + label?: string, + description?: string, + isOptional?: boolean } function setInfo<T extends Info>(param: T, info?: Info): T { if (!info) return param; if (info.description) param.description = info.description; if (info.label) param.label = info.label; + if (info.isOptional) param.isOptional = info.isOptional; return param; } -- GitLab