From 8b2fb2be77612668a02f05b5b4a6d2be0616de9f Mon Sep 17 00:00:00 2001 From: David Sehnal <david.sehnal@gmail.com> Date: Sat, 10 Nov 2018 17:38:42 +0100 Subject: [PATCH] mol-state: added StateAction --- src/mol-plugin/behavior/behavior.ts | 2 +- .../behavior/built-in/representation.ts | 2 +- src/mol-plugin/context.ts | 11 ++-- src/mol-plugin/state.ts | 2 +- src/mol-plugin/state/action.ts | 7 -- src/mol-plugin/state/base.ts | 40 ----------- src/mol-plugin/state/objects.ts | 66 +++++++++++++------ src/mol-plugin/state/transforms/data.ts | 4 +- src/mol-plugin/state/transforms/model.ts | 4 +- src/mol-plugin/state/transforms/visuals.ts | 5 +- src/mol-plugin/ui/state-tree.tsx | 2 +- src/mol-state/action.ts | 65 ++++++++++++++++++ src/mol-state/action/manager.ts | 0 src/mol-state/state.ts | 20 ++---- src/mol-state/state/selection.ts | 15 +++-- src/mol-state/transformer.ts | 6 +- 16 files changed, 146 insertions(+), 105 deletions(-) delete mode 100644 src/mol-plugin/state/action.ts delete mode 100644 src/mol-plugin/state/base.ts create mode 100644 src/mol-state/action.ts create mode 100644 src/mol-state/action/manager.ts diff --git a/src/mol-plugin/behavior/behavior.ts b/src/mol-plugin/behavior/behavior.ts index ab9025a9f..f15d4fcfd 100644 --- a/src/mol-plugin/behavior/behavior.ts +++ b/src/mol-plugin/behavior/behavior.ts @@ -4,7 +4,7 @@ * @author David Sehnal <david.sehnal@gmail.com> */ -import { PluginStateTransform, PluginStateObject } from '../state/base'; +import { PluginStateTransform, PluginStateObject } from '../state/objects'; import { Transformer } from 'mol-state'; import { Task } from 'mol-task'; import { PluginContext } from 'mol-plugin/context'; diff --git a/src/mol-plugin/behavior/built-in/representation.ts b/src/mol-plugin/behavior/built-in/representation.ts index 785acc9a4..556406db3 100644 --- a/src/mol-plugin/behavior/built-in/representation.ts +++ b/src/mol-plugin/behavior/built-in/representation.ts @@ -5,7 +5,7 @@ */ import { PluginBehavior } from '../behavior'; -import { PluginStateObject as SO } from '../../state/base'; +import { PluginStateObject as SO } from '../../state/objects'; import { EmptyLoci, Loci, areLociEqual } from 'mol-model/loci'; import { MarkerAction } from 'mol-geo/geometry/marker-data'; diff --git a/src/mol-plugin/context.ts b/src/mol-plugin/context.ts index 4ee8bedaa..326d07368 100644 --- a/src/mol-plugin/context.ts +++ b/src/mol-plugin/context.ts @@ -7,8 +7,7 @@ import { Transformer, Transform, State } from 'mol-state'; import { Canvas3D } from 'mol-canvas3d/canvas3d'; import { StateTransforms } from './state/transforms'; -import { PluginStateObject as PSO } from './state/base'; -import { PluginStateObjects as SO } from './state/objects'; +import { PluginStateObject as SO } from './state/objects'; import { RxEventHelper } from 'mol-util/rx-event-helper'; import { PluginState } from './state'; import { MolScriptBuilder } from 'mol-script/language/builder'; @@ -135,19 +134,19 @@ export class PluginContext { private initEvents() { merge(this.events.state.data.object.created, this.events.state.behavior.object.created).subscribe(o => { - if (!PSO.isBehavior(o.obj)) return; + if (!SO.isBehavior(o.obj)) return; console.log('registering behavior', o.obj.label); o.obj.data.register(); }); merge(this.events.state.data.object.removed, this.events.state.behavior.object.removed).subscribe(o => { - if (!PSO.isBehavior(o.obj)) return; + if (!SO.isBehavior(o.obj)) return; o.obj.data.unregister(); }); merge(this.events.state.data.object.replaced, this.events.state.behavior.object.replaced).subscribe(o => { - if (o.oldObj && PSO.isBehavior(o.oldObj)) o.oldObj.data.unregister(); - if (o.newObj && PSO.isBehavior(o.newObj)) o.newObj.data.register(); + if (o.oldObj && SO.isBehavior(o.oldObj)) o.oldObj.data.unregister(); + if (o.newObj && SO.isBehavior(o.newObj)) o.newObj.data.register(); }); } diff --git a/src/mol-plugin/state.ts b/src/mol-plugin/state.ts index 22075b399..b96729fd8 100644 --- a/src/mol-plugin/state.ts +++ b/src/mol-plugin/state.ts @@ -5,7 +5,7 @@ */ import { State } from 'mol-state'; -import { PluginStateObjects as SO } from './state/objects'; +import { PluginStateObject as SO } from './state/objects'; import { Camera } from 'mol-canvas3d/camera'; import { PluginBehavior } from './behavior'; diff --git a/src/mol-plugin/state/action.ts b/src/mol-plugin/state/action.ts deleted file mode 100644 index 79be247de..000000000 --- a/src/mol-plugin/state/action.ts +++ /dev/null @@ -1,7 +0,0 @@ -/** - * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. - * - * @author David Sehnal <david.sehnal@gmail.com> - */ - -// TODO actions that modify state and can be "applied" to certain state objects. \ No newline at end of file diff --git a/src/mol-plugin/state/base.ts b/src/mol-plugin/state/base.ts deleted file mode 100644 index fd2383963..000000000 --- a/src/mol-plugin/state/base.ts +++ /dev/null @@ -1,40 +0,0 @@ -/** - * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. - * - * @author David Sehnal <david.sehnal@gmail.com> - */ - -import { StateObject, Transformer } from 'mol-state'; -import { Representation } from 'mol-repr'; -import { PluginBehavior } from 'mol-plugin/behavior/behavior'; - -export type TypeClass = 'root' | 'data' | 'prop' - -export namespace PluginStateObject { - export type Any = StateObject<any, TypeInfo> - - export type TypeClass = 'Root' | 'Group' | 'Data' | 'Object' | 'Representation3D' | 'Behavior' - export interface TypeInfo { name: string, typeClass: TypeClass } - - export const Create = StateObject.factory<TypeInfo>(); - - export function isRepresentation3D(o?: Any): o is StateObject<Representation.Any, TypeInfo> { - return !!o && o.type.typeClass === 'Representation3D'; - } - - export function isBehavior(o?: Any): o is StateObject<PluginBehavior, TypeInfo> { - return !!o && o.type.typeClass === 'Behavior'; - } - - export function CreateRepresentation3D<T extends Representation.Any>(type: { name: string }) { - return Create<T>({ ...type, typeClass: 'Representation3D' }) - } - - export function CreateBehavior<T extends PluginBehavior>(type: { name: string }) { - return Create<T>({ ...type, typeClass: 'Behavior' }) - } -} - -export namespace PluginStateTransform { - export const Create = Transformer.factory('ms-plugin'); -} \ No newline at end of file diff --git a/src/mol-plugin/state/objects.ts b/src/mol-plugin/state/objects.ts index a3f344db3..337fad1e0 100644 --- a/src/mol-plugin/state/objects.ts +++ b/src/mol-plugin/state/objects.ts @@ -4,44 +4,70 @@ * @author David Sehnal <david.sehnal@gmail.com> */ -import { PluginStateObject } from './base'; import { CifFile } from 'mol-io/reader/cif'; -import { Model as _Model, Structure as _Structure } from 'mol-model/structure' +import { Model as _Model, Structure as _Structure } from 'mol-model/structure'; +import { VolumeData } from 'mol-model/volume'; +import { PluginBehavior } from 'mol-plugin/behavior/behavior'; +import { Representation } from 'mol-repr'; import { StructureRepresentation } from 'mol-repr/structure/index'; import { VolumeRepresentation } from 'mol-repr/volume'; -import { VolumeData } from 'mol-model/volume'; +import { StateObject, Transformer } from 'mol-state'; + +export type TypeClass = 'root' | 'data' | 'prop' + +export namespace PluginStateObject { + export type Any = StateObject<any, TypeInfo> + + export type TypeClass = 'Root' | 'Group' | 'Data' | 'Object' | 'Representation3D' | 'Behavior' + export interface TypeInfo { name: string, typeClass: TypeClass } + + export const Create = StateObject.factory<TypeInfo>(); + + export function isRepresentation3D(o?: Any): o is StateObject<Representation.Any, TypeInfo> { + return !!o && o.type.typeClass === 'Representation3D'; + } -const _create = PluginStateObject.Create, _createRepr3D = PluginStateObject.CreateRepresentation3D + export function isBehavior(o?: Any): o is StateObject<PluginBehavior, TypeInfo> { + return !!o && o.type.typeClass === 'Behavior'; + } -namespace PluginStateObjects { - export class Root extends _create({ name: 'Root', typeClass: 'Root' }) { } + export function CreateRepresentation3D<T extends Representation.Any>(type: { name: string }) { + return Create<T>({ ...type, typeClass: 'Representation3D' }) + } - export class Group extends _create({ name: 'Group', typeClass: 'Group' }) { } + export function CreateBehavior<T extends PluginBehavior>(type: { name: string }) { + return Create<T>({ ...type, typeClass: 'Behavior' }) + } + + export class Root extends Create({ name: 'Root', typeClass: 'Root' }) { } + + export class Group extends Create({ name: 'Group', typeClass: 'Group' }) { } export namespace Data { - export class String extends _create<string>({ name: 'String Data', typeClass: 'Data', }) { } - export class Binary extends _create<Uint8Array>({ name: 'Binary Data', typeClass: 'Data' }) { } - export class Json extends _create<any>({ name: 'JSON Data', typeClass: 'Data' }) { } - export class Cif extends _create<CifFile>({ name: 'CIF File', typeClass: 'Data' }) { } + export class String extends Create<string>({ name: 'String Data', typeClass: 'Data', }) { } + export class Binary extends Create<Uint8Array>({ name: 'Binary Data', typeClass: 'Data' }) { } + export class Json extends Create<any>({ name: 'JSON Data', typeClass: 'Data' }) { } + export class Cif extends Create<CifFile>({ name: 'CIF File', typeClass: 'Data' }) { } // TODO - // export class MultipleRaw extends _create<{ + // export class MultipleRaw extends Create<{ // [key: string]: { type: 'String' | 'Binary', data: string | Uint8Array } // }>({ name: 'Data', typeClass: 'Data', shortName: 'MD', description: 'Multiple Keyed Data.' }) { } } export namespace Molecule { - export class Trajectory extends _create<ReadonlyArray<_Model>>({ name: 'Trajectory', typeClass: 'Object' }) { } - export class Model extends _create<_Model>({ name: 'Model', typeClass: 'Object' }) { } - export class Structure extends _create<_Structure>({ name: 'Structure', typeClass: 'Object' }) { } - export class Representation3D extends _createRepr3D<StructureRepresentation<any>>({ name: 'Structure 3D' }) { } + export class Trajectory extends Create<ReadonlyArray<_Model>>({ name: 'Trajectory', typeClass: 'Object' }) { } + export class Model extends Create<_Model>({ name: 'Model', typeClass: 'Object' }) { } + export class Structure extends Create<_Structure>({ name: 'Structure', typeClass: 'Object' }) { } + export class Representation3D extends CreateRepresentation3D<StructureRepresentation<any>>({ name: 'Structure 3D' }) { } } export namespace Volume { - export class Data extends _create<VolumeData>({ name: 'Volume Data', typeClass: 'Object' }) { } - export class Representation3D extends _createRepr3D<VolumeRepresentation<any>>({ name: 'Volume 3D' }) { } + export class Data extends Create<VolumeData>({ name: 'Volume Data', typeClass: 'Object' }) { } + export class Representation3D extends CreateRepresentation3D<VolumeRepresentation<any>>({ name: 'Volume 3D' }) { } } - } -export { PluginStateObjects } \ No newline at end of file +export namespace PluginStateTransform { + export const Create = Transformer.factory('ms-plugin'); +} \ No newline at end of file diff --git a/src/mol-plugin/state/transforms/data.ts b/src/mol-plugin/state/transforms/data.ts index 1739569f6..454529c20 100644 --- a/src/mol-plugin/state/transforms/data.ts +++ b/src/mol-plugin/state/transforms/data.ts @@ -4,8 +4,8 @@ * @author David Sehnal <david.sehnal@gmail.com> */ -import { PluginStateTransform } from '../base'; -import { PluginStateObjects as SO } from '../objects'; +import { PluginStateTransform } from '../objects'; +import { PluginStateObject as SO } from '../objects'; import { Task } from 'mol-task'; import CIF from 'mol-io/reader/cif' import { PluginContext } from 'mol-plugin/context'; diff --git a/src/mol-plugin/state/transforms/model.ts b/src/mol-plugin/state/transforms/model.ts index c5ac24094..4d2a53b46 100644 --- a/src/mol-plugin/state/transforms/model.ts +++ b/src/mol-plugin/state/transforms/model.ts @@ -4,8 +4,8 @@ * @author David Sehnal <david.sehnal@gmail.com> */ -import { PluginStateTransform } from '../base'; -import { PluginStateObjects as SO } from '../objects'; +import { PluginStateTransform } from '../objects'; +import { PluginStateObject as SO } from '../objects'; import { Task } from 'mol-task'; import { Model, Format, Structure, ModelSymmetry, StructureSymmetry, QueryContext, StructureSelection } from 'mol-model/structure'; import { ParamDefinition as PD } from 'mol-util/param-definition'; diff --git a/src/mol-plugin/state/transforms/visuals.ts b/src/mol-plugin/state/transforms/visuals.ts index c4d590b56..f67ca17a2 100644 --- a/src/mol-plugin/state/transforms/visuals.ts +++ b/src/mol-plugin/state/transforms/visuals.ts @@ -6,9 +6,8 @@ import { Transformer } from 'mol-state'; import { Task } from 'mol-task'; -import { PluginStateTransform } from '../base'; -import { PluginStateObjects as SO } from '../objects'; -//import { CartoonRepresentation, DefaultCartoonProps } from 'mol-repr/structure/representation/cartoon'; +import { PluginStateTransform } from '../objects'; +import { PluginStateObject as SO } from '../objects'; import { BallAndStickRepresentation, DefaultBallAndStickProps } from 'mol-repr/structure/representation/ball-and-stick'; import { PluginContext } from 'mol-plugin/context'; diff --git a/src/mol-plugin/ui/state-tree.tsx b/src/mol-plugin/ui/state-tree.tsx index 97f4bad61..9a10114e2 100644 --- a/src/mol-plugin/ui/state-tree.tsx +++ b/src/mol-plugin/ui/state-tree.tsx @@ -6,7 +6,7 @@ import * as React from 'react'; import { PluginContext } from '../context'; -import { PluginStateObject } from 'mol-plugin/state/base'; +import { PluginStateObject } from 'mol-plugin/state/objects'; import { State } from 'mol-state' import { PluginCommands } from 'mol-plugin/command'; diff --git a/src/mol-state/action.ts b/src/mol-state/action.ts new file mode 100644 index 000000000..abc53999e --- /dev/null +++ b/src/mol-state/action.ts @@ -0,0 +1,65 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author David Sehnal <david.sehnal@gmail.com> + */ + +import { Task } from 'mol-task'; +import { UUID } from 'mol-util'; +import { ParamDefinition as PD } from 'mol-util/param-definition'; +import { StateObject, StateObjectCell } from './object'; +import { State } from './state'; +import { Transformer } from './transformer'; + +export { StateAction }; + +interface StateAction<A extends StateObject = StateObject, T = any, P = unknown> { + readonly id: UUID, + readonly definition: StateAction.Definition<A, T, P> +} + +namespace StateAction { + export type Id = string & { '@type': 'transformer-id' } + export type Params<T extends StateAction<any, any, any>> = T extends StateAction<any, any, infer P> ? P : unknown; + export type ReType<T extends StateAction<any, any, any>> = T extends StateAction<any, infer T, any> ? T : unknown; + export type ControlsFor<Props> = { [P in keyof Props]?: PD.Any } + + export interface ApplyParams<A extends StateObject = StateObject, P = unknown> { + cell: StateObjectCell, + a: A, + state: State, + params: P + } + + export interface Definition<A extends StateObject = StateObject, T = any, P = unknown> { + readonly from: StateObject.Ctor[], + 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>, + + readonly params?: Transformer<A, any, P>['definition']['params'], + + /** Test if the transform can be applied to a given node */ + isApplicable?(a: A, globalCtx: unknown): boolean + } + + export function create<A extends StateObject, T, P>(definition: Definition<A, T, P>): StateAction<A, T, P> { + return { id: UUID.create(), definition }; + } + + export function fromTransformer<T extends Transformer>(transformer: T) { + const def = transformer.definition; + 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'], + apply({ cell, state, params }) { + const tree = state.build().to(cell.transform.ref).apply(transformer, params); + return state.update(tree); + } + }) + } +} \ No newline at end of file diff --git a/src/mol-state/action/manager.ts b/src/mol-state/action/manager.ts new file mode 100644 index 000000000..e69de29bb diff --git a/src/mol-state/state.ts b/src/mol-state/state.ts index 6561cf443..a9db3b482 100644 --- a/src/mol-state/state.ts +++ b/src/mol-state/state.ts @@ -13,6 +13,7 @@ import { RuntimeContext, Task } from 'mol-task'; import { StateSelection } from './state/selection'; import { RxEventHelper } from 'mol-util/rx-event-helper'; import { StateTreeBuilder } from './tree/builder'; +import { StateAction } from './action'; export { State } @@ -80,6 +81,10 @@ class State { return StateSelection.select(selector(StateSelection.Generators), this) } + apply(action: StateAction, ref: Transform.Ref) { + + } + update(tree: StateTree | StateTreeBuilder): Task<void> { // TODO: support cell state const _tree = StateTreeBuilder.is(tree) ? tree.getTree() : tree; @@ -254,19 +259,6 @@ function doError(ctx: UpdateContext, ref: Ref, errorText: string) { } } -function findAncestor(tree: StateTree, cells: State.Cells, root: Ref, types: { type: StateObject.Type }[]): StateObjectCell | undefined { - let current = tree.nodes.get(root)!; - while (true) { - current = tree.nodes.get(current.parent)!; - const cell = cells.get(current.ref)!; - if (!cell.obj) return void 0; - for (const t of types) if (cell.obj.type === t.type) return cells.get(current.ref)!; - if (current.ref === Transform.RootRef) { - return void 0; - } - } -} - async function updateSubtree(ctx: UpdateContext, root: Ref) { setCellStatus(ctx, root, 'processing'); @@ -296,7 +288,7 @@ async function updateSubtree(ctx: UpdateContext, root: Ref) { async function updateNode(ctx: UpdateContext, currentRef: Ref) { const { oldTree, tree } = ctx; const transform = tree.nodes.get(currentRef); - const parentCell = findAncestor(tree, ctx.cells, currentRef, transform.transformer.definition.from); + const parentCell = StateSelection.findAncestorOfType(tree, ctx.cells, currentRef, transform.transformer.definition.from); if (!parentCell) { throw new Error(`No suitable parent found for '${currentRef}'`); diff --git a/src/mol-state/state/selection.ts b/src/mol-state/state/selection.ts index 6a045890d..894506d6a 100644 --- a/src/mol-state/state/selection.ts +++ b/src/mol-state/state/selection.ts @@ -123,6 +123,7 @@ namespace StateSelection { const set = new Set<string>(); const ret: StateObjectCell[] = []; for (const n of q(state)) { + if (!n) continue; if (!set.has(n.transform.ref)) { set.add(n.transform.ref); ret.push(n); @@ -169,22 +170,24 @@ namespace StateSelection { export function ofType(b: Selector, t: StateObject.Type) { return filter(b, n => n.obj ? n.obj.type === t : false); } registerModifier('ancestorOfType', ancestorOfType); - export function ancestorOfType(b: Selector, types: StateObject.Ctor[]) { return unique(mapEntity(b, (n, s) => findAncestorOfType(s, n.transform.ref, types))); } + export function ancestorOfType(b: Selector, types: StateObject.Ctor[]) { return unique(mapEntity(b, (n, s) => findAncestorOfType(s.tree, s.cells, n.transform.ref, types))); } registerModifier('parent', parent); export function parent(b: Selector) { return unique(mapEntity(b, (n, s) => s.cells.get(s.tree.nodes.get(n.transform.ref)!.parent))); } - function findAncestorOfType({ tree, cells }: State, root: string, types: StateObject.Ctor[]): StateObjectCell | undefined { + export function findAncestorOfType(tree: StateTree, cells: State.Cells, root: Transform.Ref, types: StateObject.Ctor[]): StateObjectCell | undefined { let current = tree.nodes.get(root)!, len = types.length; while (true) { current = tree.nodes.get(current.parent)!; - if (current.ref === Transform.RootRef) { - return cells.get(Transform.RootRef); - } - const obj = cells.get(current.ref)!.obj!; + const cell = cells.get(current.ref)!; + if (!cell.obj) return void 0; + const obj = cell.obj; for (let i = 0; i < len; i++) { if (obj.type === types[i].type) return cells.get(current.ref); } + if (current.ref === Transform.RootRef) { + return void 0; + } } } } diff --git a/src/mol-state/transformer.ts b/src/mol-state/transformer.ts index 375c9a950..c38b03e9c 100644 --- a/src/mol-state/transformer.ts +++ b/src/mol-state/transformer.ts @@ -8,9 +8,11 @@ import { Task } from 'mol-task'; import { StateObject } from './object'; import { Transform } from './transform'; import { ParamDefinition as PD } from 'mol-util/param-definition'; +import { StateAction } from './action'; export interface Transformer<A extends StateObject = StateObject, B extends StateObject = StateObject, P = unknown> { apply(parent: Transform.Ref, params?: P, props?: Partial<Transform.Options>): Transform<A, B, P>, + toAction(): StateAction<A, void, P>, readonly namespace: string, readonly id: Transformer.Id, readonly definition: Transformer.Definition<A, B, P> @@ -19,6 +21,7 @@ export interface Transformer<A extends StateObject = StateObject, B extends Stat export namespace Transformer { export type Id = string & { '@type': 'transformer-id' } 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 } @@ -114,7 +117,8 @@ export namespace Transformer { } const t: Transformer<A, B, P> = { - apply(parent, params, props) { return Transform.create<A, B, P>(parent, t as any, params, props); }, + apply(parent, params, props) { return Transform.create<A, B, P>(parent, t, params, props); }, + toAction() { return StateAction.fromTransformer(t); }, namespace, id, definition -- GitLab