diff --git a/src/mol-plugin/behavior/behavior.ts b/src/mol-plugin/behavior/behavior.ts index 1064ac2ad5b21afa14c16ec4444543a5e198becc..39123238e1bd546a6d661fefe8bd5fb9eaa2330b 100644 --- a/src/mol-plugin/behavior/behavior.ts +++ b/src/mol-plugin/behavior/behavior.ts @@ -31,11 +31,16 @@ namespace PluginBehavior { name: string, ctor: Ctor<P>, label?: (params: P) => { label: string, description?: string }, - display: { name: string, description?: string }, - params?: Transformer.Definition<Root, Behavior, P>['params'] + display: { + name: string, + group: string, + description?: string + }, + params?: Transformer.Definition<Root, Behavior, P>['params'], } export function create<P>(params: CreateParams<P>) { + // TODO: cache groups etc return PluginStateTransform.Create<Root, Behavior, P>({ name: params.name, display: params.display, diff --git a/src/mol-plugin/behavior/built-in/data.ts b/src/mol-plugin/behavior/built-in/data.ts index d9e6a6b745ee2ffec6d8996deedf4be2d548309d..e8107d0fc29dc7cfbb40a5a385586475608184b7 100644 --- a/src/mol-plugin/behavior/built-in/data.ts +++ b/src/mol-plugin/behavior/built-in/data.ts @@ -11,13 +11,13 @@ import { StateTree } from 'mol-state'; export const SetCurrentObject = PluginBehavior.create({ name: 'set-current-data-object-behavior', ctor: PluginBehavior.simpleCommandHandler(PluginCommands.Data.SetCurrentObject, ({ ref }, ctx) => ctx.state.data.setCurrent(ref)), - display: { name: 'Set Current Handler' } + display: { name: 'Set Current Handler', group: 'Data' } }); export const Update = PluginBehavior.create({ name: 'update-data-behavior', ctor: PluginBehavior.simpleCommandHandler(PluginCommands.Data.Update, ({ tree }, ctx) => ctx.state.updateData(tree)), - display: { name: 'Update Data Handler' } + display: { name: 'Update Data Handler', group: 'Data' } }); export const RemoveObject = PluginBehavior.create({ @@ -26,5 +26,5 @@ export const RemoveObject = PluginBehavior.create({ const tree = StateTree.build(ctx.state.data.tree).delete(ref).getTree(); ctx.state.updateData(tree); }), - display: { name: 'Remove Object Handler' } + display: { name: 'Remove Object Handler', group: 'Data' } }); \ No newline at end of file diff --git a/src/mol-plugin/behavior/built-in/representation.ts b/src/mol-plugin/behavior/built-in/representation.ts index 27c0d14348dbf6a9a5e5d8fd47a9e361f7ea0676..785acc9a4eb5048e63dd5576352648d8381a6a91 100644 --- a/src/mol-plugin/behavior/built-in/representation.ts +++ b/src/mol-plugin/behavior/built-in/representation.ts @@ -44,7 +44,7 @@ export const AddRepresentationToCanvas = PluginBehavior.create({ }); } }, - display: { name: 'Add Representation To Canvas' } + display: { name: 'Add Representation To Canvas', group: 'Data' } }); export const HighlightLoci = PluginBehavior.create({ @@ -64,7 +64,7 @@ export const HighlightLoci = PluginBehavior.create({ }); } }, - display: { name: 'Highlight Loci on Canvas' } + display: { name: 'Highlight Loci on Canvas', group: 'Data' } }); export const SelectLoci = PluginBehavior.create({ @@ -77,5 +77,5 @@ export const SelectLoci = PluginBehavior.create({ }); } }, - display: { name: 'Select Loci on Canvas' } + display: { name: 'Select Loci on Canvas', group: 'Data' } }); \ No newline at end of file diff --git a/src/mol-plugin/ui/controls.tsx b/src/mol-plugin/ui/controls.tsx index eecfb2dd2366aa1105885c9119ebe1f2e1e64905..ec6155d9fe7225f353060490035bd4e82a64416f 100644 --- a/src/mol-plugin/ui/controls.tsx +++ b/src/mol-plugin/ui/controls.tsx @@ -6,7 +6,7 @@ import * as React from 'react'; import { PluginContext } from '../context'; -import { Transform, Transformer, StateObject } from 'mol-state'; +import { Transform, Transformer } from 'mol-state'; import { ParametersComponent } from 'mol-app/component/parameters'; export class Controls extends React.Component<{ plugin: PluginContext }, { id: string }> { @@ -73,7 +73,7 @@ export class _test_CreateTransform extends React.Component<{ plugin: PluginConte render() { const obj = this.getObj(); - if (obj.state !== StateObject.StateType.Ok) { + if (obj.status !== 'ok') { // TODO filter this elsewhere return <div />; } diff --git a/src/mol-plugin/ui/state-tree.tsx b/src/mol-plugin/ui/state-tree.tsx index 01c9509152f75d1995d9905a38acb8d0fd06d5c4..8c7dc5dd0e36ea35a872c9586b93ad0c1a47791c 100644 --- a/src/mol-plugin/ui/state-tree.tsx +++ b/src/mol-plugin/ui/state-tree.tsx @@ -7,7 +7,7 @@ import * as React from 'react'; import { PluginContext } from '../context'; import { PluginStateObject } from 'mol-plugin/state/base'; -import { StateObject, State } from 'mol-state' +import { State } from 'mol-state' import { PluginCommands } from 'mol-plugin/command'; export class StateTree extends React.Component<{ plugin: PluginContext, state: State }, { }> { @@ -35,9 +35,9 @@ export class StateTreeNode extends React.Component<{ plugin: PluginContext, node PluginCommands.Data.RemoveObject.dispatch(this.props.plugin, { ref: this.props.nodeRef }); }}>X</a>]</> - if (!obj.obj) { + if (obj.status !== 'ok' || !obj.obj) { return <div style={{ borderLeft: '1px solid black', paddingLeft: '7px' }}> - {remove} {StateObject.StateType[obj.state]} {obj.errorText} + {remove} {obj.status} {obj.errorText} </div>; } const props = obj.obj!.props as PluginStateObject.Props; diff --git a/src/mol-state/util/immutable-tree.ts b/src/mol-state/immutable-tree.ts similarity index 100% rename from src/mol-state/util/immutable-tree.ts rename to src/mol-state/immutable-tree.ts diff --git a/src/mol-state/manager.ts b/src/mol-state/manager.ts new file mode 100644 index 0000000000000000000000000000000000000000..0042b15a93f4184628d575672d2570f63ac64dc6 --- /dev/null +++ b/src/mol-state/manager.ts @@ -0,0 +1,7 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author David Sehnal <david.sehnal@gmail.com> + */ + +// TODO manage snapshots etc \ No newline at end of file diff --git a/src/mol-state/object.ts b/src/mol-state/object.ts index dddfcb68b41229e7a54a39d3bbe0a4479d6fe887..ed74ec0927f263fec6efe20aa313b5e464f137a6 100644 --- a/src/mol-state/object.ts +++ b/src/mol-state/object.ts @@ -1,33 +1,22 @@ - /** * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author David Sehnal <david.sehnal@gmail.com> */ -import { Transform } from './transform'; import { UUID } from 'mol-util'; +import { Transform } from './transform'; -/** A mutable state object */ -export interface StateObject<P = any, D = any> { +export { StateObject, StateObjectBox } + +interface StateObject<P = any, D = any, Type = { }> { readonly id: UUID, readonly type: StateObject.Type, readonly props: P, readonly data: D } -export namespace StateObject { - export enum StateType { - // The object has been successfully created - Ok, - // An error occured during the creation of the object - Error, - // The object is queued to be created - Pending, - // The object is currently being created - Processing - } - +namespace StateObject { export interface Type<Info = any> { info: Info } @@ -40,7 +29,7 @@ export namespace StateObject { export function create<Props, Data, TypeInfo>(typeInfo: TypeInfo) { const dataType: Type<TypeInfo> = { info: typeInfo }; - return class implements StateObject<Props, Data> { + return class implements StateObject<Props, Data, Type<TypeInfo>> { static type = dataType; static is(obj?: StateObject): obj is StateObject<Props, Data> { return !!obj && dataType === obj.type; } id = UUID.create(); @@ -48,13 +37,25 @@ export namespace StateObject { constructor(public props: Props, public data: Data) { } } } +} + +interface StateObjectBox { + ref: Transform.Ref, + props: unknown, + + status: StateObjectBox.Status, + errorText?: string, + obj?: StateObject, + version: string +} + +namespace StateObjectBox { + export type Status = 'ok' | 'error' | 'pending' | 'processing' - export interface Node { - ref: Transform.Ref, - state: StateType, - props: unknown, - errorText?: string, - obj?: StateObject, - version: string + export interface Props { + isVisible: boolean, + isHidden: boolean, + isBound: boolean, + isExpanded: boolean } } \ No newline at end of file diff --git a/src/mol-state/selection.ts b/src/mol-state/selection.ts index 60c512855f7ab9070b5f25d93b43b70594464545..560a0ef4ca2c20ae6257bbeeb4107f1eb9de0ed9 100644 --- a/src/mol-state/selection.ts +++ b/src/mol-state/selection.ts @@ -4,13 +4,13 @@ * @author David Sehnal <david.sehnal@gmail.com> */ -import { StateObject } from './object'; +import { StateObject, StateObjectBox } from './object'; import { State } from './state'; -import { ImmutableTree } from './util/immutable-tree'; +import { ImmutableTree } from './immutable-tree'; namespace StateSelection { - export type Selector = Query | Builder | string | StateObject.Node; - export type NodeSeq = StateObject.Node[] + export type Selector = Query | Builder | string | StateObjectBox; + export type NodeSeq = StateObjectBox[] export type Query = (state: State) => NodeSeq; export function select(s: Selector, state: State) { @@ -27,8 +27,8 @@ namespace StateSelection { return query; } - function isObj(arg: any): arg is StateObject.Node { - return (arg as StateObject.Node).version !== void 0; + function isObj(arg: any): arg is StateObjectBox { + return (arg as StateObjectBox).version !== void 0; } function isBuilder(arg: any): arg is Builder { @@ -40,13 +40,13 @@ namespace StateSelection { } export interface Builder { - flatMap(f: (n: StateObject.Node) => StateObject.Node[]): Builder; - mapEntity(f: (n: StateObject.Node) => StateObject.Node): Builder; + flatMap(f: (n: StateObjectBox) => StateObjectBox[]): Builder; + mapEntity(f: (n: StateObjectBox) => StateObjectBox): Builder; unique(): Builder; parent(): Builder; first(): Builder; - filter(p: (n: StateObject.Node) => boolean): Builder; + filter(p: (n: StateObjectBox) => boolean): Builder; subtree(): Builder; children(): Builder; ofType(t: StateObject.Type): Builder; @@ -68,7 +68,7 @@ namespace StateSelection { export function byRef(...refs: string[]) { return build(() => (state: State) => { - const ret: StateObject.Node[] = []; + const ret: StateObjectBox[] = []; for (const ref of refs) { const n = state.objects.get(ref); if (!n) continue; @@ -78,13 +78,13 @@ namespace StateSelection { }); } - export function byValue(...objects: StateObject.Node[]) { return build(() => (state: State) => objects); } + export function byValue(...objects: StateObjectBox[]) { return build(() => (state: State) => objects); } registerModifier('flatMap', flatMap); - export function flatMap(b: Selector, f: (obj: StateObject.Node, state: State) => NodeSeq) { + export function flatMap(b: Selector, f: (obj: StateObjectBox, state: State) => NodeSeq) { const q = compile(b); return build(() => (state: State) => { - const ret: StateObject.Node[] = []; + const ret: StateObjectBox[] = []; for (const n of q(state)) { for (const m of f(n, state)) { ret.push(m); @@ -95,10 +95,10 @@ namespace StateSelection { } registerModifier('mapEntity', mapEntity); - export function mapEntity(b: Selector, f: (n: StateObject.Node, state: State) => StateObject.Node | undefined) { + export function mapEntity(b: Selector, f: (n: StateObjectBox, state: State) => StateObjectBox | undefined) { const q = compile(b); return build(() => (state: State) => { - const ret: StateObject.Node[] = []; + const ret: StateObjectBox[] = []; for (const n of q(state)) { const x = f(n, state); if (x) ret.push(x); @@ -112,7 +112,7 @@ namespace StateSelection { const q = compile(b); return build(() => (state: State) => { const set = new Set<string>(); - const ret: StateObject.Node[] = []; + const ret: StateObjectBox[] = []; for (const n of q(state)) { if (!set.has(n.ref)) { set.add(n.ref); @@ -133,7 +133,7 @@ namespace StateSelection { } registerModifier('filter', filter); - export function filter(b: Selector, p: (n: StateObject.Node) => boolean) { return flatMap(b, n => p(n) ? [n] : []); } + export function filter(b: Selector, p: (n: StateObjectBox) => boolean) { return flatMap(b, n => p(n) ? [n] : []); } registerModifier('subtree', subtree); export function subtree(b: Selector) { @@ -147,7 +147,7 @@ namespace StateSelection { registerModifier('children', children); export function children(b: Selector) { return flatMap(b, (n, s) => { - const nodes: StateObject.Node[] = []; + const nodes: StateObjectBox[] = []; s.tree.nodes.get(n.ref)!.children.forEach(c => nodes.push(s.objects.get(c!)!)); return nodes; }); @@ -162,7 +162,7 @@ namespace StateSelection { registerModifier('parent', parent); export function parent(b: Selector) { return unique(mapEntity(b, (n, s) => s.objects.get(s.tree.nodes.get(n.ref)!.parent))); } - function findAncestorOfType({ tree, objects }: State, root: string, type: StateObject.Type): StateObject.Node | undefined { + function findAncestorOfType({ tree, objects }: State, root: string, type: StateObject.Type): StateObjectBox | undefined { let current = tree.nodes.get(root)!; while (true) { current = tree.nodes.get(current.parent)!; diff --git a/src/mol-state/state.ts b/src/mol-state/state.ts index 149486e64e48613a24002e0a3aff010ee6ac01b7..aecbbcb655199e861f70702faaf3750677bdaef7 100644 --- a/src/mol-state/state.ts +++ b/src/mol-state/state.ts @@ -4,10 +4,10 @@ * @author David Sehnal <david.sehnal@gmail.com> */ -import { StateObject } from './object'; +import { StateObject, StateObjectBox } from './object'; import { StateTree } from './tree'; import { Transform } from './transform'; -import { ImmutableTree } from './util/immutable-tree'; +import { ImmutableTree } from './immutable-tree'; import { Transformer } from './transformer'; import { StateContext } from './context'; import { UUID } from 'mol-util'; @@ -87,7 +87,7 @@ class State { this.objects.set(tree.rootRef, { ref: tree.rootRef, obj: rootObject, - state: StateObject.StateType.Ok, + status: 'ok', version: root.version, props: { ...defaultObjectProps } }); @@ -101,7 +101,7 @@ class State { } namespace State { - export type Objects = Map<Transform.Ref, StateObject.Node> + export type Objects = Map<Transform.Ref, StateObjectBox> export interface Snapshot { readonly tree: StateTree.Serialized, @@ -177,15 +177,15 @@ namespace State { return deletes; } - function setObjectState(ctx: UpdateContext, ref: Ref, state: StateObject.StateType, errorText?: string) { + function setObjectState(ctx: UpdateContext, ref: Ref, status: StateObjectBox.Status, errorText?: string) { let changed = false; if (ctx.objects.has(ref)) { const obj = ctx.objects.get(ref)!; - changed = obj.state !== state; - obj.state = state; + changed = obj.status !== status; + obj.status = status; obj.errorText = errorText; } else { - const obj: StateObject.Node = { ref, state, version: UUID.create(), errorText, props: { ...ctx.stateCtx.defaultObjectProps } }; + const obj: StateObjectBox = { ref, status, version: UUID.create(), errorText, props: { ...ctx.stateCtx.defaultObjectProps } }; ctx.objects.set(ref, obj); changed = true; } @@ -193,7 +193,7 @@ namespace State { } function _initVisitor(t: ImmutableTree.Node<Transform>, _: any, ctx: UpdateContext) { - setObjectState(ctx, t.ref, StateObject.StateType.Pending); + setObjectState(ctx, t.ref, 'pending'); } /** Return "resolve set" */ function initObjectState(ctx: UpdateContext, roots: Ref[]) { @@ -203,7 +203,7 @@ namespace State { } function doError(ctx: UpdateContext, ref: Ref, errorText: string) { - setObjectState(ctx, ref, StateObject.StateType.Error, errorText); + setObjectState(ctx, ref, 'error', errorText); const wrap = ctx.objects.get(ref)!; if (wrap.obj) { ctx.stateCtx.events.object.removed.next({ ref }); @@ -232,11 +232,11 @@ namespace State { } async function updateSubtree(ctx: UpdateContext, root: Ref) { - setObjectState(ctx, root, StateObject.StateType.Processing); + setObjectState(ctx, root, 'processing'); try { const update = await updateNode(ctx, root); - setObjectState(ctx, root, StateObject.StateType.Ok); + setObjectState(ctx, root, 'ok'); if (update.action === 'created') { ctx.stateCtx.events.object.created.next({ ref: root, obj: update.obj! }); } else if (update.action === 'updated') { @@ -268,7 +268,7 @@ namespace State { objects.set(currentRef, { ref: currentRef, obj, - state: StateObject.StateType.Ok, + status: 'ok', version: transform.version, props: { ...ctx.stateCtx.defaultObjectProps, ...transform.defaultProps } }); @@ -283,7 +283,7 @@ namespace State { objects.set(currentRef, { ref: currentRef, obj, - state: StateObject.StateType.Ok, + status: 'ok', version: transform.version, props: { ...ctx.stateCtx.defaultObjectProps, ...current.props, ...transform.defaultProps } }); diff --git a/src/mol-state/transformer.ts b/src/mol-state/transformer.ts index d8727cfc92995351a3240959bedee94a8d87de17..f58771db7f445eb6524acbb892c94aee27acf241 100644 --- a/src/mol-state/transformer.ts +++ b/src/mol-state/transformer.ts @@ -59,7 +59,7 @@ export namespace Transformer { */ update?(params: UpdateParams<A, B, P>, globalCtx: unknown): Task<UpdateResult> | UpdateResult, - params?: { + readonly params?: { /** Check the parameters and return a list of errors if the are not valid. */ default?(a: A, globalCtx: unknown): P, /** Specify default control descriptors for the parameters */ @@ -68,7 +68,7 @@ export namespace Transformer { validate?(a: A, params: P, globalCtx: unknown): string[] | undefined, /** Optional custom parameter equality. Use deep structural equal by default. */ areEqual?(oldParams: P, newParams: P): boolean - } + }, /** Test if the transform can be applied to a given node */ isApplicable?(a: A, globalCtx: unknown): boolean, @@ -77,7 +77,7 @@ export namespace Transformer { isSerializable?(params: P): { isSerializable: true } | { isSerializable: false; reason: string }, /** Custom conversion to and from JSON */ - customSerialization?: { toJSON(params: P, obj?: B): any, fromJSON(data: any): P } + readonly customSerialization?: { toJSON(params: P, obj?: B): any, fromJSON(data: any): P } } const registry = new Map<Id, Transformer<any, any>>(); diff --git a/src/mol-state/tree.ts b/src/mol-state/tree.ts index f7fbd4955dae802b5bf23fb5535ba8370cd03a7b..d26bf4902e56bfeddc605d241f554965fd42a75d 100644 --- a/src/mol-state/tree.ts +++ b/src/mol-state/tree.ts @@ -5,7 +5,7 @@ */ import { Transform } from './transform'; -import { ImmutableTree } from './util/immutable-tree'; +import { ImmutableTree } from './immutable-tree'; import { Transformer } from './transformer'; import { StateObject } from './object'; diff --git a/src/perf-tests/state.ts b/src/perf-tests/state.ts index dd594d26c88e747ed8c30b3fa97db41986965ad3..dbe511f90b081a196d0c1fb2f4a729560bcbe8d0 100644 --- a/src/perf-tests/state.ts +++ b/src/perf-tests/state.ts @@ -68,8 +68,7 @@ function hookEvents(state: State) { state.context.events.object.created.subscribe(e => console.log('created:', e.ref)); state.context.events.object.removed.subscribe(e => console.log('removed:', e.ref)); state.context.events.object.replaced.subscribe(e => console.log('replaced:', e.ref)); - state.context.events.object.stateChanged.subscribe(e => console.log('stateChanged:', e.ref, - StateObject.StateType[state.objects.get(e.ref)!.state])); + state.context.events.object.stateChanged.subscribe(e => console.log('stateChanged:', e.ref, state.objects.get(e.ref)!.status)); state.context.events.object.updated.subscribe(e => console.log('updated:', e.ref)); }