diff --git a/src/mol-plugin/behavior/behavior.ts b/src/mol-plugin/behavior/behavior.ts index ab9025a9fc9f10b07d9475193a84acb8d402c185..f15d4fcfd5b719c0f9a1aa307e3aff1910105107 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 785acc9a4eb5048e63dd5576352648d8381a6a91..556406db37da4f4b538249a73f74503e4eee528e 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 4ee8bedaa564910a7241d6811b23b9c75a81c6f6..326d07368eaa5f4e54ebd736a85cf09fdd824b39 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 22075b3999fb20ad36b2ddb6763f7aa0fc049fa4..b96729fd84a014043487bb65bf3c9b002e0d9fb1 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 79be247dea90d8ee04439378c5ac1b6280a20145..0000000000000000000000000000000000000000 --- 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 fd2383963fd12259f5ad319a46c1a7a249ce4ba4..0000000000000000000000000000000000000000 --- 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 a3f344db3fab4a7c1ccd6ae374cec79a6e24164f..337fad1e059ff731056e61fe4a9647909c9289ef 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 1739569f6621bfc7e70b25fa44d8ac991bf2c539..454529c201535a98a69c188b9bdd70a1f0dfab51 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 c5ac2409453c6f7853e842c5a12264638018cace..4d2a53b4672237a55e3ee583d6f0053bd37fe71b 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 c4d590b569dae59e48a478bba9f1c1669485cea4..f67ca17a24aaf492a425de7581f0294e47186075 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 97f4bad61702ec4d6cef3049613375cf2034e31d..9a10114e2dfec52f6e281d878fff74061ec8fd6c 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 0000000000000000000000000000000000000000..abc53999e6bdd2a4caf32a9e6f20a1cd067641e2 --- /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 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/src/mol-state/state.ts b/src/mol-state/state.ts index 6561cf4436b047847599992cb6b92ef3efaaa08d..a9db3b482dcc305c013d0d290c1886769464dcb2 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 6a045890da09604403efd2d9135f929ec95ac975..894506d6a38a0cd8ee2cdbc75c4e0167fdb5b326 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 375c9a9506124295fb2a653f186e71e18e607468..c38b03e9c0513a9a498e3042d41038b4f4587e53 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