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

mol-state: param definition

parent 7700b34d
No related branches found
No related tags found
No related merge requests found
......@@ -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>) {
......
......@@ -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
......@@ -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));
......
......@@ -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'
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 })
}),
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
},
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);
......
......@@ -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) {
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) {
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);
......
......@@ -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) => ({
params: (a, ctx: PluginContext) => ({
type: PD.Mapped(
ctx.structureReprensentation.registry.default.name,
ctx.structureReprensentation.registry.types,
name => PD.Group(
name => PD.Group<any>(
ctx.structureReprensentation.registry.get(name).getParams(ctx.structureReprensentation.themeCtx, a.data),
{ label: 'Type Parameters' }
),
{ label: 'Type' }
)
})
},
{ label: 'Type' })
}),
apply({ a, params }, plugin: PluginContext) {
return Task.create('Structure Representation', async ctx => {
const provider = plugin.structureReprensentation.registry.get(params.type.name)
......
......@@ -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
};
......
......@@ -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
......
......@@ -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);
......
......@@ -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,
export const EmptyParams = () => ({});
/** 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 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; }
})
......
......@@ -138,14 +138,10 @@ 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 {
// TODO: should this be here?
if (shallowEqual(transform.params, params)) {
return false;
}
}
if (!this.changedNodes) {
this.changedNodes = true;
......
......@@ -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;
}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment