Bladeren bron

mol-state: infer transformer params from definition

David Sehnal 6 jaren geleden
bovenliggende
commit
98f1525023

+ 2 - 2
src/mol-plugin/state/actions/basic.ts

@@ -12,7 +12,7 @@ import { StateSelection } from 'mol-state/state/selection';
 import { CartoonParams } from 'mol-repr/structure/representation/cartoon';
 import { BallAndStickParams } from 'mol-repr/structure/representation/ball-and-stick';
 import { Download } from '../transforms/data';
-import { StateTree } from 'mol-state';
+import { StateTree, Transformer } from 'mol-state';
 import { StateTreeBuilder } from 'mol-state/tree/builder';
 import { PolymerIdColorThemeParams } from 'mol-theme/color/polymer-id';
 import { UniformSizeThemeParams } from 'mol-theme/size/uniform';
@@ -43,7 +43,7 @@ namespace ObtainStructureHelpers {
     ];
 
     export function getControls(key: string) { return (ControlMap as any)[key]; }
-    export function getUrl(src: DownloadStructure.Source): Download.Params {
+    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}` };

+ 1 - 0
src/mol-plugin/state/objects.ts

@@ -70,4 +70,5 @@ export namespace PluginStateObject {
 
 export namespace PluginStateTransform {
     export const Create = Transformer.factory('ms-plugin');
+    export const BuiltIn = Transformer.factory1('ms-plugin');
 }

+ 26 - 23
src/mol-plugin/state/transforms/data.ts

@@ -14,20 +14,21 @@ import { Transformer } from 'mol-state';
 import { readFromFile } from 'mol-util/data-source';
 
 export { Download }
-namespace Download { export interface Params { url: string, isBinary?: boolean, label?: string } }
-const Download = PluginStateTransform.Create<SO.Root, SO.Data.String | SO.Data.Binary, Download.Params>({
+type Download = typeof Download
+const Download = PluginStateTransform.BuiltIn({
     name: 'download',
+    from: [SO.Root],
+    to: [SO.Data.String, SO.Data.Binary],
+    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.makeOptional(PD.Text('')),
+        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'
     },
-    from: [SO.Root],
-    to: [SO.Data.String, SO.Data.Binary],
-    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 => {
             const data = await globalCtx.fetch(p.url, p.isBinary ? 'binary' : 'string').runInContext(ctx);
@@ -47,21 +48,22 @@ const Download = PluginStateTransform.Create<SO.Root, SO.Data.String | SO.Data.B
 });
 
 export { ReadFile }
-namespace ReadFile { export interface Params { file: File, isBinary?: boolean, label?: string } }
-const ReadFile = PluginStateTransform.Create<SO.Root, SO.Data.String | SO.Data.Binary, ReadFile.Params>({
+type ReadFile = typeof ReadFile
+const ReadFile = PluginStateTransform.BuiltIn({
     name: 'read-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'
     },
-    from: [SO.Root],
-    to: [SO.Data.String, SO.Data.Binary],
-    params: () => ({
-        file: PD.File(),
-        label: PD.Text('', { isOptional: true }),
-        isBinary: PD.Boolean(false, { description: 'If true, open file as as binary (string otherwise)', isOptional: true })
-    }),
-    apply({ params: p }, globalCtx: PluginContext) {
+    apply({ params: p }) {
         return Task.create('Open File', async ctx => {
             const data = await readFromFile(p.file, p.isBinary ? 'binary' : 'string').runInContext(ctx);
             return p.isBinary
@@ -80,15 +82,16 @@ const ReadFile = PluginStateTransform.Create<SO.Root, SO.Data.String | SO.Data.B
 });
 
 export { ParseCif }
-namespace ParseCif { export interface Params { } }
-const ParseCif = PluginStateTransform.Create<SO.Data.String | SO.Data.Binary, SO.Data.Cif, ParseCif.Params>({
+type ParseCif = typeof ParseCif
+const ParseCif = PluginStateTransform.BuiltIn({
     name: 'parse-cif',
+    from: [SO.Data.String, SO.Data.Binary],
+    to: SO.Data.Cif
+})({
     display: {
         name: 'Parse CIF',
         description: 'Parse CIF from String or Binary data'
     },
-    from: [SO.Data.String, SO.Data.Binary],
-    to: [SO.Data.Cif],
     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);

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

@@ -23,7 +23,8 @@ namespace StateObject {
     }
 
     export type Type<Cls extends string = string> = { name: string, typeClass: Cls }
-    export type Ctor = { new(...args: any[]): StateObject, type: any }
+    export type Ctor<T extends StateObject = StateObject> = { new(...args: any[]): T, type: any }
+    export type From<C extends Ctor> = C extends Ctor<infer T> ? T : never
 
     export function create<Data, T extends Type>(type: T) {
         return class implements StateObject<Data, T> {

+ 66 - 6
src/mol-state/transformer.ts

@@ -56,10 +56,7 @@ export namespace Transformer {
     /** 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,
-        readonly from: StateObject.Ctor[],
-        readonly to: StateObject.Ctor[],
+    export interface DefinitionBase<A extends StateObject = StateObject, B extends StateObject = StateObject, P extends {} = {}> {
         readonly display?: { readonly name: string, readonly description?: string },
 
         /**
@@ -78,8 +75,6 @@ export namespace Transformer {
         /** Determine if the transformer can be applied automatically on UI change. Default is false. */
         canAutoUpdate?(params: AutoUpdateParams<A, B, P>, globalCtx: unknown): boolean,
 
-        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,
 
@@ -90,6 +85,13 @@ export namespace Transformer {
         readonly customSerialization?: { toJSON(params: P, obj?: B): any, fromJSON(data: any): P }
     }
 
+    export interface Definition<A extends StateObject = StateObject, B extends StateObject = StateObject, P extends {} = {}> extends DefinitionBase<A, B, P> {
+        readonly name: string,
+        readonly from: StateObject.Ctor[],
+        readonly to: StateObject.Ctor[],
+        params?(a: A, globalCtx: unknown): { [K in keyof P]: PD.Any },
+    }
+
     const registry = new Map<Id, Transformer<any, any>>();
     const fromTypeIndex: Map<StateObject.Type, Transformer[]> = new Map();
 
@@ -140,6 +142,64 @@ export namespace Transformer {
         return <A extends StateObject, B extends StateObject, P extends {} = {}>(definition: Definition<A, B, P>) => create(namespace, definition);
     }
 
+    export function factory1(namespace: string) {
+        return Builder.build(namespace);
+    }
+
+    export namespace Builder {
+        type ParamDefinition<P> = { [K in keyof P]-?: PD.Base<P[K]> }
+
+        export interface Type<A extends StateObject.Ctor, B extends StateObject.Ctor> {
+            name: string,
+            from: A | A[],
+            to: B | B[]
+        }
+
+        export interface TypeAndParams<A extends StateObject.Ctor, B extends StateObject.Ctor, P> extends Type<A, B> {
+            params: ParamDefinition<P>
+        }
+
+        export interface TypeAndParamProvider<A extends StateObject.Ctor, B extends StateObject.Ctor, P> extends Type<A, B> {
+            paramProvider(a: A, globalCtx: unknown): ParamDefinition<P>
+        }
+
+        export interface Root {
+            <A extends StateObject.Ctor, B extends StateObject.Ctor>(info: Type<A, B>): Define<StateObject.From<A>, StateObject.From<B>, {}>,
+            <A extends StateObject.Ctor, B extends StateObject.Ctor, P>(info: TypeAndParams<A, B, P>): Define<StateObject.From<A>, StateObject.From<B>, Params<P>>,
+            <A extends StateObject.Ctor, B extends StateObject.Ctor, P>(info: TypeAndParamProvider<A, B, P>): Define<StateObject.From<A>, StateObject.From<B>, Params<P>>        
+        }
+
+        type Optionals<P> = { [K in keyof P]-?: undefined extends P[K] ? K : never }[keyof P]
+        type NonOptionals<P> = { [K in keyof P]-?: undefined extends P[K] ? never: K }[keyof P]
+        type Params<P> = Pick<P, NonOptionals<P>> & Partial<Pick<P, Optionals<P>>>
+
+        export interface Define<A extends StateObject, B extends StateObject, P> {
+            (def: DefinitionBase<A, B, P>): Transformer<A, B, P>
+        }
+
+        function root(namespace: string, info: Type<any, any> & TypeAndParams<any, any, any> & TypeAndParamProvider<any, any, any>): Define<any, any, any> {
+            return def => create(namespace, {
+                name: info.name,
+                from: info.from instanceof Array ? info.from : [info.from],
+                to: info.to instanceof Array ? info.to : [info.to],
+                params: info.paramProvider
+                    ? info.paramProvider as any
+                    : info.params
+                    ? () => info.params
+                    : void 0,
+                ...def
+            });
+        }
+
+        export function build(namespace: string): Root {
+            return (info: any) => root(namespace, info);
+        }
+    }
+
+    export function build(namespace: string): Builder.Root {
+        return Builder.build(namespace);
+    }
+
     export const ROOT = create<any, any, {}>('build-in', {
         name: 'root',
         from: [],

+ 2 - 2
src/mol-state/tree/builder.ts

@@ -59,9 +59,9 @@ namespace StateTreeBuilder {
             return new To(this.state, t.ref, this.root);
         }
 
-        update<T extends Transformer<A, any, any>>(transformer: T, params: (old: Transformer.Params<T>) => Transformer.Params<T>): Root
+        update<T extends Transformer<any, A, any>>(transformer: T, params: (old: Transformer.Params<T>) => Transformer.Params<T>): Root
         update(params: any): Root
-        update<T extends Transformer<A, any, any>>(paramsOrTransformer: T, provider?: (old: Transformer.Params<T>) => Transformer.Params<T>) {
+        update<T extends Transformer<any, A, any>>(paramsOrTransformer: T, provider?: (old: Transformer.Params<T>) => Transformer.Params<T>) {
             let params: any;
             if (provider) {
                 const old = this.state.tree.transforms.get(this.ref)!;

+ 6 - 1
src/mol-util/param-definition.ts

@@ -14,8 +14,8 @@ export namespace ParamDefinition {
     export interface Info {
         label?: string,
         description?: string,
+        isHidden?: boolean,
         isOptional?: boolean,
-        isHidden?: boolean
     }
 
     function setInfo<T extends Info>(param: T, info?: Info): T {
@@ -31,6 +31,11 @@ export namespace ParamDefinition {
         defaultValue: T
     }
 
+    export function makeOptional<T>(p: Base<T>): Base<T | undefined> {
+        p.isOptional = true;
+        return p;
+    }
+
     export interface Value<T> extends Base<T> {
         type: 'value'
     }