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

mol-state: infer transformer params from definition

parent ea356d1d
No related branches found
No related tags found
No related merge requests found
......@@ -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}` };
......
......@@ -70,4 +70,5 @@ export namespace PluginStateObject {
export namespace PluginStateTransform {
export const Create = Transformer.factory('ms-plugin');
export const BuiltIn = Transformer.factory1('ms-plugin');
}
\ No newline at end of file
......@@ -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);
......
......@@ -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> {
......
......@@ -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: [],
......
......@@ -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)!;
......
......@@ -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'
}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please to comment