Browse Source

mol-plugin: 1st state saving prototype

David Sehnal 6 years ago
parent
commit
1a2d2a8482

+ 11 - 1
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' }
 });

+ 2 - 2
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');
+export const RemoveObject = PluginCommand<{ ref: Transform.Ref }>('ms-data', 'remove-object');

+ 1 - 0
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();
 

+ 19 - 0
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);
 }

+ 4 - 4
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');
     }
 

+ 6 - 5
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;
         });
     }

+ 4 - 2
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>
     }

+ 1 - 1
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 />

+ 8 - 5
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>;
     }

+ 2 - 2
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) {

+ 2 - 1
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; }
     })
 }

+ 4 - 1
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) } }
         }

+ 4 - 4
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) {