diff --git a/src/mol-plugin/behavior.ts b/src/mol-plugin/behavior.ts index 78fdaf11f59a02ae5e05cbae8eee5117b11f343b..db04070ecb343ec2c46b18dff1d545a6c8569b9b 100644 --- a/src/mol-plugin/behavior.ts +++ b/src/mol-plugin/behavior.ts @@ -6,7 +6,9 @@ export * from './behavior/behavior' import * as Data from './behavior/data' +import * as Representation from './behavior/representation' export const PluginBehaviors = { - Data + Data, + Representation } \ No newline at end of file diff --git a/src/mol-plugin/behavior/behavior.ts b/src/mol-plugin/behavior/behavior.ts index 08d4c2af524386eb2e8e19fb5b4a33590414debc..ac667eee751a42cf025f9f62e41400a91f27343c 100644 --- a/src/mol-plugin/behavior/behavior.ts +++ b/src/mol-plugin/behavior/behavior.ts @@ -10,6 +10,7 @@ import { Transformer } from 'mol-state'; import { Task } from 'mol-task'; import { PluginContext } from 'mol-plugin/context'; import { PluginCommand } from '../command'; +import { Observable } from 'rxjs'; export { PluginBehavior } @@ -29,14 +30,14 @@ namespace PluginBehavior { ctor: Ctor<P>, label?: (params: P) => { label: string, description?: string }, display: { name: string, description?: string }, - params?: Transformer.Definition<SO.Root, SO.Behavior, P>['params'] + params?: Transformer.Definition<SO.BehaviorRoot, SO.Behavior, P>['params'] } export function create<P>(params: CreateParams<P>) { - return PluginStateTransform.Create<SO.Root, SO.Behavior, P>({ + return PluginStateTransform.Create<SO.BehaviorRoot, SO.Behavior, P>({ name: params.name, display: params.display, - from: [SO.Root], + from: [SO.BehaviorRoot], to: [SO.Behavior], params: params.params, apply({ params: p }, ctx: PluginContext) { @@ -53,7 +54,7 @@ namespace PluginBehavior { }); } - export function commandHandler<T>(cmd: PluginCommand<T>, action: (data: T, ctx: PluginContext) => void | Promise<void>) { + export function simpleCommandHandler<T>(cmd: PluginCommand<T>, action: (data: T, ctx: PluginContext) => void | Promise<void>) { return class implements PluginBehavior<undefined> { private sub: PluginCommand.Subscription | undefined = void 0; register(): void { @@ -66,4 +67,25 @@ namespace PluginBehavior { constructor(private ctx: PluginContext) { } } } + + export abstract class Handler implements PluginBehavior<undefined> { + private subs: PluginCommand.Subscription[] = []; + protected subscribeCommand<T>(cmd: PluginCommand<T>, action: PluginCommand.Action<T>) { + this.subs.push(cmd.subscribe(this.ctx, action)); + } + protected subscribeObservable<T>(o: Observable<T>, action: (v: T) => void) { + this.subs.push(o.subscribe(action)); + } + protected track<T>(sub: PluginCommand.Subscription) { + this.subs.push(sub); + } + abstract register(): void; + unregister() { + for (const s of this.subs) s.unsubscribe(); + this.subs = []; + } + constructor(protected ctx: PluginContext) { + + } + } } \ No newline at end of file diff --git a/src/mol-plugin/behavior/data.ts b/src/mol-plugin/behavior/data.ts index 1b7e1467893e43b73f766f0615279e4c9c8dbfac..8f68045783fe2176ea7f254ba06ab47cb6933311 100644 --- a/src/mol-plugin/behavior/data.ts +++ b/src/mol-plugin/behavior/data.ts @@ -1,4 +1,3 @@ - /** * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. * @@ -8,28 +7,14 @@ import { PluginBehavior } from './behavior'; import { PluginCommands } from 'mol-plugin/command'; -// export class SetCurrentObject implements PluginBehavior<undefined> { -// private sub: PluginCommand.Subscription | undefined = void 0; - -// register(): void { -// this.sub = PluginCommands.Data.SetCurrentObject.subscribe(this.ctx, ({ ref }) => this.ctx.state.data.setCurrent(ref)); -// } -// unregister(): void { -// if (this.sub) this.sub.unsubscribe(); -// this.sub = void 0; -// } - -// constructor(private ctx: PluginContext) { } -// } - export const SetCurrentObject = PluginBehavior.create({ name: 'set-current-data-object-behavior', - ctor: PluginBehavior.commandHandler(PluginCommands.Data.SetCurrentObject, ({ ref }, ctx) => ctx.state.data.setCurrent(ref)), + ctor: PluginBehavior.simpleCommandHandler(PluginCommands.Data.SetCurrentObject, ({ ref }, ctx) => ctx.state.data.setCurrent(ref)), display: { name: 'Set Current Handler' } }); export const Update = PluginBehavior.create({ name: 'update-data-behavior', - ctor: PluginBehavior.commandHandler(PluginCommands.Data.Update, ({ tree }, ctx) => ctx.runTask(ctx.state.data.update(tree))), + ctor: PluginBehavior.simpleCommandHandler(PluginCommands.Data.Update, ({ tree }, ctx) => ctx.runTask(ctx.state.data.update(tree))), display: { name: 'Update Data Handler' } }); \ No newline at end of file diff --git a/src/mol-plugin/behavior/representation.ts b/src/mol-plugin/behavior/representation.ts index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..f2b1ddf54b886a5f9b57e5d03dd74c1caa425fcb 100644 --- a/src/mol-plugin/behavior/representation.ts +++ b/src/mol-plugin/behavior/representation.ts @@ -0,0 +1,50 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author David Sehnal <david.sehnal@gmail.com> + */ + +import { PluginBehavior } from './behavior'; +import { PluginStateObjects as SO } from '../state/objects'; + +class _AddRepresentationToCanvas extends PluginBehavior.Handler { + register(): void { + this.subscribeObservable(this.ctx.events.state.data.object.created, o => { + if (!SO.StructureRepresentation3D.is(o.obj)) return; + this.ctx.canvas3d.add(o.obj.data.repr); + this.ctx.canvas3d.requestDraw(true); + }); + this.subscribeObservable(this.ctx.events.state.data.object.updated, o => { + const oo = o.obj; + if (!SO.StructureRepresentation3D.is(oo)) return; + this.ctx.canvas3d.add(oo.data.repr); + this.ctx.canvas3d.requestDraw(true); + }); + this.subscribeObservable(this.ctx.events.state.data.object.removed, o => { + const oo = o.obj; + console.log('removed', o.ref, oo && oo.type); + if (!SO.StructureRepresentation3D.is(oo)) return; + this.ctx.canvas3d.remove(oo.data.repr); + console.log('removed from canvas', o.ref); + this.ctx.canvas3d.requestDraw(true); + oo.data.repr.destroy(); + }); + this.subscribeObservable(this.ctx.events.state.data.object.replaced, o => { + if (o.oldObj && SO.StructureRepresentation3D.is(o.oldObj)) { + this.ctx.canvas3d.remove(o.oldObj.data.repr); + this.ctx.canvas3d.requestDraw(true); + o.oldObj.data.repr.destroy(); + } + if (o.newObj && SO.StructureRepresentation3D.is(o.newObj)) { + this.ctx.canvas3d.add(o.newObj.data.repr); + this.ctx.canvas3d.requestDraw(true); + } + }); + } +} + +export const AddRepresentationToCanvas = PluginBehavior.create({ + name: 'add-representation-to-canvas', + ctor: _AddRepresentationToCanvas, + display: { name: 'Add Representation To Canvas' } +}); \ No newline at end of file diff --git a/src/mol-plugin/command/command.ts b/src/mol-plugin/command/command.ts index 6148919f19c34cc3646dc99453008cdd3ac3f422..a76186d5aeaaaae2753c8dd3228c80a035eda76a 100644 --- a/src/mol-plugin/command/command.ts +++ b/src/mol-plugin/command/command.ts @@ -87,7 +87,7 @@ namespace PluginCommand { /** Resolves after all actions have completed */ dispatch<T>(cmd: PluginCommand<T> | Id, params: T) { return new Promise<void>((resolve, reject) => { - if (!this.disposing) { + if (this.disposing) { reject('disposed'); return; } diff --git a/src/mol-plugin/context.ts b/src/mol-plugin/context.ts index 5fb5d42ba7121ad2115f07fad62f63c8a9688df7..ff593b78ad3c1e437386fc0b7780f48e7624abc8 100644 --- a/src/mol-plugin/context.ts +++ b/src/mol-plugin/context.ts @@ -4,15 +4,17 @@ * @author David Sehnal <david.sehnal@gmail.com> */ -import { StateTree, StateSelection, Transformer } from 'mol-state'; +import { StateTree, StateSelection, Transformer, Transform } from 'mol-state'; import Canvas3D from 'mol-canvas3d/canvas3d'; import { StateTransforms } from './state/transforms'; import { PluginStateObjects as SO } from './state/objects'; import { RxEventHelper } from 'mol-util/rx-event-helper'; import { PluginState } from './state'; import { MolScriptBuilder } from 'mol-script/language/builder'; -import { PluginCommand } from './command'; +import { PluginCommand, PluginCommands } from './command'; import { Task } from 'mol-task'; +import { merge } from 'rxjs'; +import { PluginBehaviors } from './behavior'; export class PluginContext { private disposed = false; @@ -22,10 +24,17 @@ export class PluginContext { readonly commands = new PluginCommand.Manager(); readonly events = { - data: this.state.data.context.events + state: { + data: this.state.data.context.events, + behavior: this.state.behavior.context.events + } }; readonly behaviors = { + state: { + data: this.state.data.context.behaviors, + behavior: this.state.behavior.context.behaviors + }, command: this.commands.behaviour }; @@ -66,6 +75,21 @@ export class PluginContext { this.disposed = true; } + async _test_initBehaviours() { + const tree = StateTree.build(this.state.behavior.tree) + .toRoot().apply(PluginBehaviors.Data.SetCurrentObject) + .and().toRoot().apply(PluginBehaviors.Data.Update) + .and().toRoot().apply(PluginBehaviors.Representation.AddRepresentationToCanvas) + .getTree(); + + await this.state.updateBehaviour(tree); + } + + _test_applyTransform(a: Transform.Ref, transformer: Transformer, params: any) { + const tree = StateTree.build(this.state.data.tree).to(a).apply(transformer, params).getTree(); + PluginCommands.Data.Update.dispatch(this, { tree }); + } + _test_createState(url: string) { const b = StateTree.build(this.state.data.tree); @@ -94,23 +118,27 @@ export class PluginContext { } private initEvents() { - this.state.data.context.events.object.created.subscribe(o => { - if (!SO.StructureRepresentation3D.is(o.obj)) return; - console.log('adding repr', o.obj.data.repr); - this.canvas3d.add(o.obj.data.repr); - this.canvas3d.requestDraw(true); + merge(this.events.state.data.object.created, this.events.state.behavior.object.created).subscribe(o => { + console.log('creating', o.obj.type); + if (!SO.Behavior.is(o.obj)) return; + o.obj.data.register(); + }); + + merge(this.events.state.data.object.removed, this.events.state.behavior.object.removed).subscribe(o => { + if (!SO.Behavior.is(o.obj)) return; + o.obj.data.unregister(); }); - this.state.data.context.events.object.updated.subscribe(o => { - const oo = o.obj; - if (!SO.StructureRepresentation3D.is(oo)) return; - console.log('updating repr', oo.data.repr); - this.canvas3d.add(oo.data.repr); - this.canvas3d.requestDraw(true); + + merge(this.events.state.data.object.replaced, this.events.state.behavior.object.replaced).subscribe(o => { + if (o.oldObj && SO.Behavior.is(o.oldObj)) o.oldObj.data.unregister(); + if (o.newObj && SO.Behavior.is(o.newObj)) o.newObj.data.register(); }); } _test_centerView() { - const sel = StateSelection.select('structure', this.state.data); + const sel = StateSelection.select(StateSelection.root().subtree().ofType(SO.Structure.type), this.state.data); + if (!sel.length) return; + const center = (sel[0].obj! as SO.Structure).data.boundary.sphere.center; console.log({ sel, center, rc: this.canvas3d.reprCount }); this.canvas3d.center(center); @@ -135,6 +163,8 @@ export class PluginContext { constructor() { this.initEvents(); + + this._test_initBehaviours(); } // logger = ; diff --git a/src/mol-plugin/state.ts b/src/mol-plugin/state.ts index b9de2611e58498780a885a253172f2b08651c91f..7118673aa5c7ce466b745c6c538241f68325059d 100644 --- a/src/mol-plugin/state.ts +++ b/src/mol-plugin/state.ts @@ -12,41 +12,47 @@ export { PluginState } class PluginState { readonly data: State; - readonly behaviour: State; - - readonly canvas = { - camera: CombinedCamera.create() - }; + readonly behavior: State; getSnapshot(): PluginState.Snapshot { return { data: this.data.getSnapshot(), - behaviour: this.behaviour.getSnapshot(), - canvas: { - camera: { ...this.canvas.camera } + behaviour: this.behavior.getSnapshot(), + canvas3d: { + camera: { ...this.plugin.canvas3d.camera } } }; } setSnapshot(snapshot: PluginState.Snapshot) { - // TODO events - this.behaviour.setSnapshot(snapshot.behaviour); + this.behavior.setSnapshot(snapshot.behaviour); this.data.setSnapshot(snapshot.data); - this.canvas.camera = { ...snapshot.canvas.camera }; + + // TODO: handle camera + // console.log({ old: { ...this.plugin.canvas3d.camera }, new: snapshot.canvas3d.camera }); + // CombinedCamera.copy(snapshot.canvas3d.camera, this.plugin.canvas3d.camera); + // CombinedCamera.update(this.plugin.canvas3d.camera); + // this.plugin.canvas3d.center + // console.log({ copied: { ...this.plugin.canvas3d.camera } }); + // this.plugin.canvas3d.requestDraw(true); + // console.log('updated camera'); + } + + updateData(tree: StateTree) { + return this.plugin.runTask(this.data.update(tree)); } - async updateData(tree: StateTree) { - // TODO: "task observer" - await this.data.update(tree).run(p => console.log(p), 250); + updateBehaviour(tree: StateTree) { + return this.plugin.runTask(this.behavior.update(tree)); } dispose() { this.data.dispose(); } - constructor(globalContext: unknown) { - this.data = State.create(new SO.Root({ label: 'Root' }, { }), { globalContext }); - this.behaviour = State.create(new SO.Root({ label: 'Root' }, { }), { globalContext }); + constructor(private plugin: import('./context').PluginContext) { + this.data = State.create(new SO.DataRoot({ label: 'Root' }, { }), { globalContext: plugin }); + this.behavior = State.create(new SO.BehaviorRoot({ label: 'Root' }, { }), { globalContext: plugin }); } } @@ -54,6 +60,8 @@ namespace PluginState { export interface Snapshot { data: State.Snapshot, behaviour: State.Snapshot, - canvas: PluginState['canvas'] + canvas3d: { + camera: CombinedCamera + } } } diff --git a/src/mol-plugin/state/objects.ts b/src/mol-plugin/state/objects.ts index 53b57be5a948c13ec11d82d249b7bb5be6e1dbe2..e043e4d2fda7a24670b8c4091da40e6b051d1d89 100644 --- a/src/mol-plugin/state/objects.ts +++ b/src/mol-plugin/state/objects.ts @@ -12,7 +12,9 @@ import { StructureRepresentation } from 'mol-repr/structure/index'; const _create = PluginStateObject.Create namespace PluginStateObjects { - export class Root extends _create({ name: 'Root', shortName: 'R', typeClass: 'Root', description: 'Where everything begins.' }) { } + export class DataRoot extends _create({ name: 'Root', shortName: 'R', typeClass: 'Root', description: 'Where everything begins.' }) { } + export class BehaviorRoot extends _create({ name: 'Root', shortName: 'R', typeClass: 'Root', description: 'Where everything begins.' }) { } + export class Group extends _create({ name: 'Group', shortName: 'G', typeClass: 'Group', description: 'A group on entities.' }) { } export class Behavior extends _create<import('../behavior').PluginBehavior>({ name: 'Behavior', shortName: 'B', typeClass: 'Behavior', description: 'Modifies plugin functionality.' }) { } diff --git a/src/mol-plugin/state/transforms/data.ts b/src/mol-plugin/state/transforms/data.ts index 7d4aaf81d4af067d8fa1891684f0071af5c903c7..67899a9c666663370ce38be4717ef5d37428776e 100644 --- a/src/mol-plugin/state/transforms/data.ts +++ b/src/mol-plugin/state/transforms/data.ts @@ -13,15 +13,18 @@ import { ParamDefinition as PD } from 'mol-util/param-definition'; 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>({ +const Download = PluginStateTransform.Create<SO.DataRoot, SO.Data.String | SO.Data.Binary, Download.Params>({ name: 'download', display: { name: 'Download', description: 'Download string or binary data from the specified URL' }, - from: [SO.Root], + from: [SO.DataRoot], to: [SO.Data.String, SO.Data.Binary], params: { + default: () => ({ + url: 'https://www.ebi.ac.uk/pdbe/static/entry/1cbs_updated.cif' + }), controls: () => ({ url: PD.Text('URL', 'Resource URL. Must be the same domain or support CORS.', ''), isBinary: PD.Boolean('Binary', 'If true, download data as binary (string otherwise)', false) diff --git a/src/mol-plugin/state/transforms/visuals.ts b/src/mol-plugin/state/transforms/visuals.ts index 978143e45412d955336322086aaaee9239bcd8c1..368bed5383c0d0858de9ba08fdcba96233c3f0eb 100644 --- a/src/mol-plugin/state/transforms/visuals.ts +++ b/src/mol-plugin/state/transforms/visuals.ts @@ -15,6 +15,7 @@ export { CreateStructureRepresentation } namespace CreateStructureRepresentation { export interface Params { } } const CreateStructureRepresentation = PluginStateTransform.Create<SO.Structure, SO.StructureRepresentation3D, CreateStructureRepresentation.Params>({ name: 'create-structure-representation', + display: { name: 'Create 3D Representation' }, from: [SO.Structure], to: [SO.StructureRepresentation3D], apply({ a, params }) { diff --git a/src/mol-plugin/ui/controls.tsx b/src/mol-plugin/ui/controls.tsx index 33b01ff280ab637a4ccd4b459a5932ceb4f7abf6..b591ce97f28429411b15fe7f6c25800c5a606c7c 100644 --- a/src/mol-plugin/ui/controls.tsx +++ b/src/mol-plugin/ui/controls.tsx @@ -6,6 +6,8 @@ import * as React from 'react'; import { PluginContext } from '../context'; +import { Transform, Transformer, StateObject } from 'mol-state'; +import { ParametersComponent } from 'mol-app/component/parameters'; export class Controls extends React.Component<{ plugin: PluginContext }, { id: string }> { state = { id: '1grm' }; @@ -16,6 +18,16 @@ export class Controls extends React.Component<{ plugin: PluginContext }, { id: s this.props.plugin._test_createState(url); } + private _snap:any = void 0; + private getSnapshot = () => { + this._snap = this.props.plugin.state.getSnapshot(); + console.log(btoa(JSON.stringify(this._snap))); + } + private setSnapshot = () => { + if (!this._snap) return; + this.props.plugin.state.setSnapshot(this._snap); + } + render() { return <div> <input type='text' defaultValue={this.state.id} onChange={e => this.setState({ id: e.currentTarget.value })} /> @@ -23,6 +35,55 @@ export class Controls extends React.Component<{ plugin: PluginContext }, { id: s <button onClick={() => this.props.plugin._test_centerView()}>Center View</button><br/> <button onClick={() => this.props.plugin._test_nextModel()}>Next Model</button><br/> <button onClick={() => this.props.plugin._test_playModels()}>Play Models</button><br/> + <hr /> + <button onClick={this.getSnapshot}>Get Snapshot</button> + <button onClick={this.setSnapshot}>Set Snapshot</button> </div>; } +} + +export class _test_CreateTransform extends React.Component<{ plugin: PluginContext, nodeRef: Transform.Ref, transformer: Transformer }, { params: any }> { + private getObj() { + const obj = this.props.plugin.state.data.objects.get(this.props.nodeRef)!; + return obj; + } + + private getDefaultParams() { + const p = this.props.transformer.definition.params; + if (!p || !p.default) return { }; + const obj = this.getObj(); + if (!obj.obj) return { }; + return p.default(obj.obj, this.props.plugin); + } + + private getParamDef() { + const p = this.props.transformer.definition.params; + if (!p || !p.controls) return { }; + const obj = this.getObj(); + if (!obj.obj) return { }; + return p.controls(obj.obj, this.props.plugin); + } + + private create() { + console.log(this.props.transformer.definition.name, this.state.params); + this.props.plugin._test_applyTransform(this.props.nodeRef, this.props.transformer, this.state.params); + } + + state = { params: this.getDefaultParams() } + + render() { + const obj = this.getObj(); + if (obj.state !== StateObject.StateType.Ok) { + // TODO filter this elsewhere + return <div />; + } + + const t = this.props.transformer; + + return <div key={`${this.props.nodeRef} ${this.props.transformer.id}`}> + <div style={{ borderBottom: '1px solid #999'}}>{(t.definition.display && t.definition.display.name) || t.definition.name}</div> + <ParametersComponent params={this.getParamDef()} values={this.state.params as any} onChange={params => this.setState({ params })} /> + <button onClick={() => this.create()} style={{ width: '100%' }}>Create</button> + </div> + } } \ No newline at end of file diff --git a/src/mol-plugin/ui/plugin.tsx b/src/mol-plugin/ui/plugin.tsx index b5a301b8560cde72e5ecfffc512a9ddafc39da9d..949fb997ada3af12e8d6e94a80944c95092dfab8 100644 --- a/src/mol-plugin/ui/plugin.tsx +++ b/src/mol-plugin/ui/plugin.tsx @@ -6,17 +6,22 @@ import * as React from 'react'; import { PluginContext } from '../context'; -import { Tree } from './tree'; +import { StateTree } from './state-tree'; import { Viewport } from './viewport'; -import { Controls } from './controls'; +import { Controls, _test_CreateTransform } from './controls'; +import { Transformer } from 'mol-state'; + +// TODO: base object with subscribe helpers export class Plugin extends React.Component<{ plugin: PluginContext }, { }> { render() { return <div style={{ position: 'absolute', width: '100%', height: '100%' }}> - <div style={{ position: 'absolute', width: '250px', height: '100%' }}> - <Tree plugin={this.props.plugin} /> + <div style={{ position: 'absolute', width: '350px', height: '100%', overflowY: 'scroll' }}> + <StateTree plugin={this.props.plugin} /> + <hr /> + <_test_CurrentObject plugin={this.props.plugin} /> </div> - <div style={{ position: 'absolute', left: '250px', right: '250px', height: '100%' }}> + <div style={{ position: 'absolute', left: '350px', right: '250px', height: '100%' }}> <Viewport plugin={this.props.plugin} /> </div> <div style={{ position: 'absolute', width: '250px', right: '0', height: '100%' }}> @@ -24,4 +29,28 @@ export class Plugin extends React.Component<{ plugin: PluginContext }, { }> { </div> </div>; } +} + +export class _test_CurrentObject extends React.Component<{ plugin: PluginContext }, { }> { + componentWillMount() { + this.props.plugin.behaviors.state.data.currentObject.subscribe(() => this.forceUpdate()); + } + render() { + const ref = this.props.plugin.behaviors.state.data.currentObject.value.ref; + // const n = this.props.plugin.state.data.tree.nodes.get(ref)!; + const obj = this.props.plugin.state.data.objects.get(ref)!; + + const type = obj && obj.obj ? obj.obj.type : void 0; + + const transforms = type + ? Transformer.fromType(type) + : [] + return <div> + Current Ref: {this.props.plugin.behaviors.state.data.currentObject.value.ref} + <hr /> + { + transforms.map((t, i) => <_test_CreateTransform key={`${t.id} ${ref} ${i}`} plugin={this.props.plugin} transformer={t} nodeRef={ref} />) + } + </div>; + } } \ No newline at end of file diff --git a/src/mol-plugin/ui/tree.tsx b/src/mol-plugin/ui/state-tree.tsx similarity index 51% rename from src/mol-plugin/ui/tree.tsx rename to src/mol-plugin/ui/state-tree.tsx index c5ded6963a737d43649a57418881c2c9d6b687ae..8c4a5c5d628aeff23b6f3af624aa383887f8b080 100644 --- a/src/mol-plugin/ui/tree.tsx +++ b/src/mol-plugin/ui/state-tree.tsx @@ -8,21 +8,23 @@ import * as React from 'react'; import { PluginContext } from '../context'; import { PluginStateObject } from 'mol-plugin/state/base'; import { StateObject } from 'mol-state' +import { PluginCommands } from 'mol-plugin/command'; -export class Tree extends React.Component<{ plugin: PluginContext }, { }> { - +export class StateTree extends React.Component<{ plugin: PluginContext }, { }> { componentWillMount() { - this.props.plugin.events.data.updated.subscribe(() => this.forceUpdate()); + this.props.plugin.events.state.data.updated.subscribe(() => this.forceUpdate()); } render() { - const n = this.props.plugin.state.data.tree.nodes.get(this.props.plugin.state.data.tree.rootRef)!; + // const n = this.props.plugin.state.data.tree.nodes.get(this.props.plugin.state.data.tree.rootRef)!; + const n = this.props.plugin.state.data.tree.rootRef; return <div> - {n.children.map(c => <TreeNode plugin={this.props.plugin} nodeRef={c!} key={c} />)} + <StateTreeNode plugin={this.props.plugin} nodeRef={n} key={n} /> + { /* n.children.map(c => <StateTreeNode plugin={this.props.plugin} nodeRef={c!} key={c} />) */} </div>; } } -export class TreeNode extends React.Component<{ plugin: PluginContext, nodeRef: string }, { }> { +export class StateTreeNode extends React.Component<{ plugin: PluginContext, nodeRef: string }, { }> { render() { const n = this.props.plugin.state.data.tree.nodes.get(this.props.nodeRef)!; const obj = this.props.plugin.state.data.objects.get(this.props.nodeRef)!; @@ -33,10 +35,14 @@ export class TreeNode extends React.Component<{ plugin: PluginContext, nodeRef: } const props = obj.obj!.props as PluginStateObject.Props; return <div style={{ borderLeft: '1px solid black', paddingLeft: '5px' }}> - {props.label} {props.description ? <small>{props.description}</small> : void 0} + <a href='#' onClick={e => { + e.preventDefault(); + PluginCommands.Data.SetCurrentObject.dispatch(this.props.plugin, { ref: this.props.nodeRef }); + }}>{props.label}</a> + {props.description ? <small>{props.description}</small> : void 0} {n.children.size === 0 ? void 0 - : <div style={{ marginLeft: '10px' }}>{n.children.map(c => <TreeNode plugin={this.props.plugin} nodeRef={c!} key={c} />)}</div> + : <div style={{ marginLeft: '10px' }}>{n.children.map(c => <StateTreeNode plugin={this.props.plugin} nodeRef={c!} key={c} />)}</div> } </div>; } diff --git a/src/mol-state/state.ts b/src/mol-state/state.ts index 5f5e411892741dff2953b3553553c14eb4622412..c6660c0038daea876beeb4eccff59e89996ad16e 100644 --- a/src/mol-state/state.ts +++ b/src/mol-state/state.ts @@ -43,8 +43,8 @@ class State { setSnapshot(snapshot: State.Snapshot): void { const tree = StateTree.fromJSON(snapshot.tree); - // TODO: support props - this.update(tree); + // TODO: support props and async + this.update(tree).run(); } setCurrent(ref: Transform.Ref) { @@ -128,9 +128,10 @@ namespace State { const roots = findUpdateRoots(ctx.objects, ctx.tree); const deletes = findDeletes(ctx); for (const d of deletes) { + const obj = ctx.objects.has(d) ? ctx.objects.get(d)!.obj : void 0; ctx.objects.delete(d); ctx.transformCache.delete(d); - ctx.stateCtx.events.object.removed.next({ ref: d }); + ctx.stateCtx.events.object.removed.next({ ref: d, obj }); // TODO: handle current object change } diff --git a/src/mol-state/tree.ts b/src/mol-state/tree.ts index b8e569a2852a04892f42a7974182c31851f0456c..c21c11d9f9a3478e80e3c50f53c0e7456b5efd9d 100644 --- a/src/mol-state/tree.ts +++ b/src/mol-state/tree.ts @@ -52,8 +52,8 @@ namespace StateTree { export class Root implements Builder { private state: State; - to<A extends StateObject>(ref: Transform.Ref) { return new To<A>(this.state, ref); } - toRoot<A extends StateObject>() { return new To<A>(this.state, this.state.tree.rootRef as any); } + to<A extends StateObject>(ref: Transform.Ref) { return new To<A>(this.state, ref, this); } + toRoot<A extends StateObject>() { return new To<A>(this.state, this.state.tree.rootRef as any, this); } delete(ref: Transform.Ref) { this.state.tree.remove(ref); return this; } getTree(): StateTree { return this.state.tree.asImmutable(); } constructor(tree: StateTree) { this.state = { tree: ImmutableTree.asTransient(tree) } } @@ -63,12 +63,14 @@ namespace StateTree { apply<T extends Transformer<A, any, any>>(tr: T, params?: Transformer.Params<T>, props?: Partial<Transform.Options>): To<Transformer.To<T>> { const t = tr.apply(params, props); this.state.tree.add(this.ref, t); - return new To(this.state, t.ref); + return new To(this.state, t.ref, this.root); } + and() { return this.root; } + getTree(): StateTree { return this.state.tree.asImmutable(); } - constructor(private state: State, private ref: Transform.Ref) { + constructor(private state: State, private ref: Transform.Ref, private root: Root) { if (!this.state.tree.nodes.has(ref)) { throw new Error(`Could not find node '${ref}'.`); }