diff --git a/src/mol-plugin/behavior/data.ts b/src/mol-plugin/behavior/data.ts index 8f68045783fe2176ea7f254ba06ab47cb6933311..730e3c2176e6e074b09188ca950659e5f5352f10 100644 --- a/src/mol-plugin/behavior/data.ts +++ b/src/mol-plugin/behavior/data.ts @@ -6,6 +6,7 @@ import { PluginBehavior } from './behavior'; import { PluginCommands } from 'mol-plugin/command'; +import { StateTree } from 'mol-state'; export const SetCurrentObject = PluginBehavior.create({ name: 'set-current-data-object-behavior', @@ -15,6 +16,15 @@ export const SetCurrentObject = PluginBehavior.create({ export const Update = PluginBehavior.create({ name: 'update-data-behavior', - ctor: PluginBehavior.simpleCommandHandler(PluginCommands.Data.Update, ({ tree }, ctx) => ctx.runTask(ctx.state.data.update(tree))), + ctor: PluginBehavior.simpleCommandHandler(PluginCommands.Data.Update, ({ tree }, ctx) => ctx.state.updateData(tree)), display: { name: 'Update Data Handler' } +}); + +export const RemoveObject = PluginBehavior.create({ + name: 'remove-object-data-behavior', + ctor: PluginBehavior.simpleCommandHandler(PluginCommands.Data.RemoveObject, ({ ref }, ctx) => { + const tree = StateTree.build(ctx.state.data.tree).delete(ref).getTree(); + ctx.state.updateData(tree); + }), + display: { name: 'Remove Object Handler' } }); \ No newline at end of file diff --git a/src/mol-plugin/command/data.ts b/src/mol-plugin/command/data.ts index e74a410c9962ec9fd8f178eb60cc34d28982622e..b9965e7ed2b1ed252718b87f1e532e248e5f3b66 100644 --- a/src/mol-plugin/command/data.ts +++ b/src/mol-plugin/command/data.ts @@ -5,9 +5,9 @@ */ import { PluginCommand } from './command'; -import { Transform, StateTree, Transformer } from 'mol-state'; +import { Transform, StateTree } from 'mol-state'; export const SetCurrentObject = PluginCommand<{ ref: Transform.Ref }>('ms-data', 'set-current-object'); export const Update = PluginCommand<{ tree: StateTree }>('ms-data', 'update'); export const UpdateObject = PluginCommand<{ ref: Transform.Ref, params: any }>('ms-data', 'update-object'); -export const CreateObject = PluginCommand<{ parentRef?: Transform.Ref, transformer: Transformer, params: any }>('ms-data', 'create-object'); \ No newline at end of file +export const RemoveObject = PluginCommand<{ ref: Transform.Ref }>('ms-data', 'remove-object'); \ No newline at end of file diff --git a/src/mol-plugin/context.ts b/src/mol-plugin/context.ts index ff593b78ad3c1e437386fc0b7780f48e7624abc8..d0911e400c1fc1eb6d225c0adb6fbb1bbed577e6 100644 --- a/src/mol-plugin/context.ts +++ b/src/mol-plugin/context.ts @@ -79,6 +79,7 @@ export class PluginContext { const tree = StateTree.build(this.state.behavior.tree) .toRoot().apply(PluginBehaviors.Data.SetCurrentObject) .and().toRoot().apply(PluginBehaviors.Data.Update) + .and().toRoot().apply(PluginBehaviors.Data.RemoveObject) .and().toRoot().apply(PluginBehaviors.Representation.AddRepresentationToCanvas) .getTree(); diff --git a/src/mol-plugin/index.ts b/src/mol-plugin/index.ts index 203eb0c8e0a6569382277d22cfd3d38ae95f10c7..f54fd023beeac8facb7b407428a9a7f352c82722 100644 --- a/src/mol-plugin/index.ts +++ b/src/mol-plugin/index.ts @@ -9,8 +9,27 @@ import { Plugin } from './ui/plugin' import * as React from 'react'; import * as ReactDOM from 'react-dom'; +function getParam(name: string, regex: string): string { + let r = new RegExp(`${name}=(${regex})[&]?`, 'i'); + return decodeURIComponent(((window.location.search || '').match(r) || [])[1] || ''); +} + export function createPlugin(target: HTMLElement): PluginContext { const ctx = new PluginContext(); ReactDOM.render(React.createElement(Plugin, { plugin: ctx }), target); + + try { + trySetSnapshot(ctx); + } catch (e) { + console.warn('Failed to load snapshot', e); + } + return ctx; +} + +function trySetSnapshot(ctx: PluginContext) { + const snapshot = getParam('snapshot', `(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?`); + if (!snapshot) return; + const data = JSON.parse(atob(snapshot)); + setTimeout(() => ctx.state.setSnapshot(data), 250); } \ No newline at end of file diff --git a/src/mol-plugin/state.ts b/src/mol-plugin/state.ts index 7118673aa5c7ce466b745c6c538241f68325059d..a6892cba4e1ace0181b003bff2bad9faca577219 100644 --- a/src/mol-plugin/state.ts +++ b/src/mol-plugin/state.ts @@ -24,9 +24,9 @@ class PluginState { }; } - setSnapshot(snapshot: PluginState.Snapshot) { - this.behavior.setSnapshot(snapshot.behaviour); - this.data.setSnapshot(snapshot.data); + async setSnapshot(snapshot: PluginState.Snapshot) { + await this.behavior.setSnapshot(snapshot.behaviour); + await this.data.setSnapshot(snapshot.data); // TODO: handle camera // console.log({ old: { ...this.plugin.canvas3d.camera }, new: snapshot.canvas3d.camera }); @@ -34,7 +34,7 @@ class PluginState { // CombinedCamera.update(this.plugin.canvas3d.camera); // this.plugin.canvas3d.center // console.log({ copied: { ...this.plugin.canvas3d.camera } }); - // this.plugin.canvas3d.requestDraw(true); + this.plugin.canvas3d.requestDraw(true); // console.log('updated camera'); } diff --git a/src/mol-plugin/state/transforms/visuals.ts b/src/mol-plugin/state/transforms/visuals.ts index 368bed5383c0d0858de9ba08fdcba96233c3f0eb..cf00496b79efc1467fdb156b07f8efae072f1c5e 100644 --- a/src/mol-plugin/state/transforms/visuals.ts +++ b/src/mol-plugin/state/transforms/visuals.ts @@ -10,6 +10,7 @@ import { PluginStateTransform } from '../base'; import { PluginStateObjects as SO } from '../objects'; //import { CartoonRepresentation, DefaultCartoonProps } from 'mol-repr/structure/representation/cartoon'; import { BallAndStickRepresentation, DefaultBallAndStickProps } from 'mol-repr/structure/representation/ball-and-stick'; +import { PluginContext } from 'mol-plugin/context'; export { CreateStructureRepresentation } namespace CreateStructureRepresentation { export interface Params { } } @@ -18,16 +19,16 @@ const CreateStructureRepresentation = PluginStateTransform.Create<SO.Structure, display: { name: 'Create 3D Representation' }, from: [SO.Structure], to: [SO.StructureRepresentation3D], - apply({ a, params }) { + apply({ a, params }, plugin: PluginContext) { return Task.create('Structure Representation', async ctx => { const repr = BallAndStickRepresentation(); // CartoonRepresentation(); - await repr.createOrUpdate({ /* TODO add `webgl: WebGLContext` */ }, DefaultBallAndStickProps, a.data).runInContext(ctx); - return new SO.StructureRepresentation3D({ label: 'Cartoon' }, { repr }); + await repr.createOrUpdate({ webgl: plugin.canvas3d.webgl }, DefaultBallAndStickProps, a.data).runInContext(ctx); + return new SO.StructureRepresentation3D({ label: 'Visual Repr.' }, { repr }); }); }, - update({ a, b }) { + update({ a, b }, plugin: PluginContext) { return Task.create('Structure Representation', async ctx => { - await b.data.repr.createOrUpdate({ /* TODO add `webgl: WebGLContext` */ }, b.data.repr.props, a.data).runInContext(ctx); + await b.data.repr.createOrUpdate({ webgl: plugin.canvas3d.webgl }, b.data.repr.props, a.data).runInContext(ctx); return Transformer.UpdateResult.Updated; }); } diff --git a/src/mol-plugin/ui/controls.tsx b/src/mol-plugin/ui/controls.tsx index b591ce97f28429411b15fe7f6c25800c5a606c7c..eecfb2dd2366aa1105885c9119ebe1f2e1e64905 100644 --- a/src/mol-plugin/ui/controls.tsx +++ b/src/mol-plugin/ui/controls.tsx @@ -18,7 +18,7 @@ export class Controls extends React.Component<{ plugin: PluginContext }, { id: s this.props.plugin._test_createState(url); } - private _snap:any = void 0; + private _snap: any = void 0; private getSnapshot = () => { this._snap = this.props.plugin.state.getSnapshot(); console.log(btoa(JSON.stringify(this._snap))); @@ -82,7 +82,9 @@ export class _test_CreateTransform extends React.Component<{ plugin: PluginConte 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 })} /> + <ParametersComponent params={this.getParamDef()} values={this.state.params as any} onChange={(k, v) => { + this.setState({ params: { ...this.state.params, [k]: v } }); + }} /> <button onClick={() => this.create()} style={{ width: '100%' }}>Create</button> </div> } diff --git a/src/mol-plugin/ui/plugin.tsx b/src/mol-plugin/ui/plugin.tsx index 949fb997ada3af12e8d6e94a80944c95092dfab8..598d5f26d386188d3bcb7061369bd6a5372c3271 100644 --- a/src/mol-plugin/ui/plugin.tsx +++ b/src/mol-plugin/ui/plugin.tsx @@ -15,7 +15,7 @@ import { Transformer } from 'mol-state'; export class Plugin extends React.Component<{ plugin: PluginContext }, { }> { render() { - return <div style={{ position: 'absolute', width: '100%', height: '100%' }}> + return <div style={{ position: 'absolute', width: '100%', height: '100%', fontFamily: 'monospace' }}> <div style={{ position: 'absolute', width: '350px', height: '100%', overflowY: 'scroll' }}> <StateTree plugin={this.props.plugin} /> <hr /> diff --git a/src/mol-plugin/ui/state-tree.tsx b/src/mol-plugin/ui/state-tree.tsx index 8c4a5c5d628aeff23b6f3af624aa383887f8b080..7ad666396a3fd28332b28e5a525fd61f2041e541 100644 --- a/src/mol-plugin/ui/state-tree.tsx +++ b/src/mol-plugin/ui/state-tree.tsx @@ -34,15 +34,18 @@ export class StateTreeNode extends React.Component<{ plugin: PluginContext, node </div>; } const props = obj.obj!.props as PluginStateObject.Props; - return <div style={{ borderLeft: '1px solid black', paddingLeft: '5px' }}> - <a href='#' onClick={e => { + const type = obj.obj!.type.info as PluginStateObject.TypeInfo; + return <div style={{ borderLeft: '1px solid #999', paddingLeft: '7px' }}> + [<a href='#' onClick={e => { + e.preventDefault(); + PluginCommands.Data.RemoveObject.dispatch(this.props.plugin, { ref: this.props.nodeRef }); + }}>X</a>][<span title={type.description}>{ type.shortName }</span>] <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} + }}>{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 => <StateTreeNode plugin={this.props.plugin} nodeRef={c!} key={c} />)}</div> + : <div style={{ marginLeft: '3px' }}>{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 c6660c0038daea876beeb4eccff59e89996ad16e..149486e64e48613a24002e0a3aff010ee6ac01b7 100644 --- a/src/mol-state/state.ts +++ b/src/mol-state/state.ts @@ -41,10 +41,10 @@ class State { }; } - setSnapshot(snapshot: State.Snapshot): void { + setSnapshot(snapshot: State.Snapshot) { const tree = StateTree.fromJSON(snapshot.tree); // TODO: support props and async - this.update(tree).run(); + return this.update(tree).run(); } setCurrent(ref: Transform.Ref) { diff --git a/src/mol-state/transformer.ts b/src/mol-state/transformer.ts index c6b2129d5f0e967031181570bd60fbb72a8e6161..d8727cfc92995351a3240959bedee94a8d87de17 100644 --- a/src/mol-state/transformer.ts +++ b/src/mol-state/transformer.ts @@ -133,6 +133,7 @@ export namespace Transformer { name: 'root', from: [], to: [], - apply() { throw new Error('should never be applied'); } + apply() { throw new Error('should never be applied'); }, + update() { return UpdateResult.Unchanged; } }) } \ No newline at end of file diff --git a/src/mol-state/tree.ts b/src/mol-state/tree.ts index c21c11d9f9a3478e80e3c50f53c0e7456b5efd9d..f7fbd4955dae802b5bf23fb5535ba8370cd03a7b 100644 --- a/src/mol-state/tree.ts +++ b/src/mol-state/tree.ts @@ -54,7 +54,10 @@ namespace StateTree { private state: State; 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; } + 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) } } } diff --git a/src/mol-state/util/immutable-tree.ts b/src/mol-state/util/immutable-tree.ts index 421f8c3bb9c0f2b53ef239dc2427fae1d3d0f974..dd7ce8f90afa53c5580857edfff29f1b585e1c4f 100644 --- a/src/mol-state/util/immutable-tree.ts +++ b/src/mol-state/util/immutable-tree.ts @@ -219,11 +219,11 @@ export namespace ImmutableTree { } remove<T>(ref: ImmutableTree.Ref): Node<T>[] { - const { nodes, mutations, mutate } = this; + const { nodes, mutations } = this; const node = nodes.get(ref); if (!node) return []; const parent = nodes.get(node.parent)!; - const children = mutate(parent.ref).children; + const children = this.mutate(parent.ref).children; const st = subtreePostOrder(this, node); if (ref !== this.rootRef) children.delete(ref); for (const n of st) { @@ -234,10 +234,10 @@ export namespace ImmutableTree { } removeChildren(ref: ImmutableTree.Ref): Node<T>[] { - const { nodes, mutations, mutate } = this; + const { nodes, mutations } = this; let node = nodes.get(ref); if (!node || !node.children.size) return []; - node = mutate(ref); + node = this.mutate(ref); const st = subtreePostOrder(this, node); node.children.clear(); for (const n of st) {