Browse Source

mol-plugin: state manager

David Sehnal 6 years ago
parent
commit
74251c0423

+ 22 - 0
src/mol-plugin/behavior/static/state.ts

@@ -7,6 +7,7 @@
 import { PluginCommands } from '../../command';
 import { PluginContext } from '../../context';
 import { StateTree, Transform, State } from 'mol-state';
+import { PluginStateSnapshotManager } from 'mol-plugin/state/snapshots';
 
 export function registerDefault(ctx: PluginContext) {
     SetCurrentObject(ctx);
@@ -15,6 +16,7 @@ export function registerDefault(ctx: PluginContext) {
     RemoveObject(ctx);
     ToggleExpanded(ctx);
     ToggleVisibility(ctx);
+    Snapshots(ctx);
 }
 
 export function SetCurrentObject(ctx: PluginContext) {
@@ -50,4 +52,24 @@ function setVisibility(state: State, root: Transform.Ref, value: boolean) {
 
 function setVisibilityVisitor(t: Transform, tree: StateTree, ctx: { state: State, value: boolean }) {
     ctx.state.updateCellState(t.ref, { isHidden: ctx.value });
+}
+
+export function Snapshots(ctx: PluginContext) {
+    PluginCommands.State.Snapshots.Clear.subscribe(ctx, () => {
+        ctx.state.snapshots.clear();
+    });
+
+    PluginCommands.State.Snapshots.Remove.subscribe(ctx, ({ id }) => {
+        ctx.state.snapshots.remove(id);
+    });
+
+    PluginCommands.State.Snapshots.Add.subscribe(ctx, ({ name, description }) => {
+        const entry = PluginStateSnapshotManager.Entry(name || new Date().toLocaleTimeString(), ctx.state.getSnapshot(), description);
+        ctx.state.snapshots.add(entry);
+    });
+
+    PluginCommands.State.Snapshots.Apply.subscribe(ctx, ({ id }) => {
+        const e = ctx.state.snapshots.getEntry(id);
+        return ctx.state.setSnapshot(e.snapshot);
+    });
 }

+ 8 - 1
src/mol-plugin/command/state.ts

@@ -18,4 +18,11 @@ export const RemoveObject = PluginCommand<{ state: State, ref: Transform.Ref }>(
 
 export const ToggleExpanded = PluginCommand<{ state: State, ref: Transform.Ref }>({ isImmediate: true });
 
-export const ToggleVisibility = PluginCommand<{ state: State, ref: Transform.Ref }>({ isImmediate: true });
+export const ToggleVisibility = PluginCommand<{ state: State, ref: Transform.Ref }>({ isImmediate: true });
+
+export const Snapshots = {
+    Add: PluginCommand<{ name?: string, description?: string }>({ isImmediate: true }),
+    Remove: PluginCommand<{ id: string }>({ isImmediate: true }),
+    Apply: PluginCommand<{ id: string }>({ isImmediate: true }),
+    Clear: PluginCommand<{ }>({ isImmediate: true }),
+}

+ 2 - 1
src/mol-plugin/context.ts

@@ -29,7 +29,8 @@ export class PluginContext {
         state: {
             data: this.state.data.events,
             behavior: this.state.behavior.events,
-            cameraSnapshots: this.state.cameraSnapshots.events
+            cameraSnapshots: this.state.cameraSnapshots.events,
+            snapshots: this.state.snapshots.events,
         }
     };
 

+ 4 - 1
src/mol-plugin/state.ts

@@ -9,13 +9,16 @@ import { PluginStateObject as SO } from './state/objects';
 import { Camera } from 'mol-canvas3d/camera';
 import { PluginBehavior } from './behavior';
 import { CameraSnapshotManager } from './state/camera';
+import { PluginStateSnapshotManager } from './state/snapshots';
 
 export { PluginState }
 
 class PluginState {
     readonly data: State;
     readonly behavior: State;
-    readonly cameraSnapshots: CameraSnapshotManager = new CameraSnapshotManager();
+    readonly cameraSnapshots = new CameraSnapshotManager();
+
+    readonly snapshots = new PluginStateSnapshotManager();
 
     getSnapshot(): PluginState.Snapshot {
         return {

+ 65 - 0
src/mol-plugin/state/snapshots.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 { OrderedMap } from 'immutable';
+import { UUID } from 'mol-util';
+import { RxEventHelper } from 'mol-util/rx-event-helper';
+import { PluginState } from '../state';
+
+export { PluginStateSnapshotManager }
+
+class PluginStateSnapshotManager {
+    private ev = RxEventHelper.create();
+    private _entries = OrderedMap<string, PluginStateSnapshotManager.Entry>().asMutable();
+
+    readonly events = {
+        changed: this.ev()
+    };
+
+    get entries() { return this._entries; }
+
+    getEntry(id: string) {
+        return this._entries.get(id);
+    }
+
+    remove(id: string) {
+        if (!this._entries.has(id)) return;
+        this._entries.delete(id);
+        this.events.changed.next();
+    }
+
+    add(e: PluginStateSnapshotManager.Entry) {
+        this._entries.set(e.id, e);
+        this.events.changed.next();
+    }
+
+    clear() {
+        if (this._entries.size === 0) return;
+        this._entries = OrderedMap<string, PluginStateSnapshotManager.Entry>().asMutable();
+        this.events.changed.next();
+    }
+
+    dispose() {
+        this.ev.dispose();
+    }
+}
+
+namespace PluginStateSnapshotManager {
+    export interface Entry {
+        id: UUID,
+        name: string,
+        description?: string,
+        snapshot: PluginState.Snapshot
+    }
+
+    export function Entry(name: string, snapshot: PluginState.Snapshot, description?: string): Entry {
+        return { id: UUID.create22(), name, snapshot, description };
+    }
+
+    export interface StateSnapshot {
+        entries: Entry[]
+    }
+}

+ 67 - 0
src/mol-plugin/ui/camera.tsx

@@ -0,0 +1,67 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+import { PluginCommands } from 'mol-plugin/command';
+import * as React from 'react';
+import { PluginComponent } from './base';
+
+export class CameraSnapshots extends PluginComponent<{ }, { }> {
+    render() {
+        return <div>
+            <h3>Camera Snapshots</h3>
+            <CameraSnapshotControls />
+            <CameraSnapshotList />
+        </div>;
+    }
+}
+
+class CameraSnapshotControls extends PluginComponent<{ }, { name: string, description: string }> {
+    state = { name: '', description: '' };
+
+    add = () => {
+        PluginCommands.Camera.Snapshots.Add.dispatch(this.plugin, this.state);
+        this.setState({ name: '', description: '' })
+    }
+
+    clear = () => {
+        PluginCommands.Camera.Snapshots.Clear.dispatch(this.plugin, {});
+    }
+
+    render() {
+        return <div>
+            <input type='text' value={this.state.name} placeholder='Name...' style={{ width: '33%', display: 'block', float: 'left' }} onChange={e => this.setState({ name: e.target.value })} />
+            <input type='text' value={this.state.description} placeholder='Description...' style={{ width: '67%', display: 'block' }} onChange={e => this.setState({ description: e.target.value })} />
+            <button style={{ float: 'right' }} onClick={this.clear}>Clear</button>
+            <button onClick={this.add}>Add</button>
+        </div>;
+    }
+}
+
+class CameraSnapshotList extends PluginComponent<{ }, { }> {
+    componentDidMount() {
+        this.subscribe(this.plugin.events.state.cameraSnapshots.changed, () => this.forceUpdate());
+    }
+
+    apply(id: string) {
+        return () => PluginCommands.Camera.Snapshots.Apply.dispatch(this.plugin, { id });
+    }
+
+    remove(id: string) {
+        return () => {
+            PluginCommands.Camera.Snapshots.Remove.dispatch(this.plugin, { id });
+        }
+    }
+
+    render() {
+        return <ul style={{ listStyle: 'none' }}>
+            {this.plugin.state.cameraSnapshots.entries.valueSeq().map(e =><li key={e!.id}>
+                <button onClick={this.apply(e!.id)}>Set</button>
+                &nbsp;{e!.name} <small>{e!.description}</small>
+                <button onClick={this.remove(e!.id)} style={{ float: 'right' }}>X</button>
+            </li>)}
+        </ul>;
+    }
+}

+ 3 - 78
src/mol-plugin/ui/controls.tsx

@@ -13,23 +13,10 @@ import { UpdateTrajectory } from 'mol-plugin/state/actions/basic';
 import { PluginComponent } from './base';
 
 export class Controls extends PluginComponent<{ }, { }> {
-    state = { id: '1grm' };
-
-    private _snap: any = void 0;
-    private getSnapshot = () => {
-        this._snap = this.plugin.state.getSnapshot();
-        console.log(btoa(JSON.stringify(this._snap)));
-    }
-    private setSnapshot = () => {
-        if (!this._snap) return;
-        this.plugin.state.setSnapshot(this._snap);
-    }
-
     render() {
-        return <div>
-            <button onClick={this.getSnapshot}>Get Snapshot</button>
-            <button onClick={this.setSnapshot}>Set Snapshot</button>
-        </div>;
+        return <>
+
+        </>;
     }
 }
 
@@ -163,66 +150,4 @@ export class _test_UpdateTransform extends PluginComponent<{ state: State, nodeR
             <button onClick={() => this.update()} style={{ width: '100%' }}>Update</button>
         </div>
     }
-}
-
-export class CameraSnapshots extends PluginComponent<{ }, { }> {
-    render() {
-        return <div>
-            <h3>Camera Snapshots</h3>
-            <CameraSnapshotControls />
-            <CameraSnapshotList />
-        </div>;
-    }
-}
-
-class CameraSnapshotControls extends PluginComponent<{ }, { name: string, description: string }> {
-    componentDidMount() {
-        this.subscribe(this.plugin.events.state.cameraSnapshots.changed, () => this.forceUpdate());
-    }
-
-    state = { name: '', description: '' };
-
-    add = () => {
-        PluginCommands.Camera.Snapshots.Add.dispatch(this.plugin, this.state);
-        this.setState({ name: '', description: '' })
-    }
-
-    clear = () => {
-        PluginCommands.Camera.Snapshots.Clear.dispatch(this.plugin, {});
-    }
-
-    render() {
-        return <div>
-            <input type='text' value={this.state.name} placeholder='Name...' style={{ width: '33%', display: 'block', float: 'left' }} onChange={e => this.setState({ name: e.target.value })} />
-            <input type='text' value={this.state.description} placeholder='Description...' style={{ width: '67%', display: 'block' }} onChange={e => this.setState({ description: e.target.value })} />
-            <button style={{ float: 'right' }} onClick={this.clear}>Clear</button>
-            <button onClick={this.add}>Add</button>
-        </div>;
-    }
-}
-
-class CameraSnapshotList extends PluginComponent<{ }, { }> {
-    componentDidMount() {
-        this.subscribe(this.plugin.events.state.cameraSnapshots.changed, () => this.forceUpdate());
-    }
-
-    apply(id: string) {
-        return () => PluginCommands.Camera.Snapshots.Apply.dispatch(this.plugin, { id });
-    }
-
-    remove(id: string) {
-        return () => {
-            PluginCommands.Camera.Snapshots.Remove.dispatch(this.plugin, { id });
-        }
-    }
-
-    render() {
-        return <ul style={{ listStyle: 'none' }}>
-            {this.plugin.state.cameraSnapshots.entries.valueSeq().map(e =><li key={e!.id}>
-                <button onClick={this.apply(e!.id)}>Set</button>
-                &nbsp;{e!.name} <small>{e!.description}</small>
-                <button onClick={this.remove(e!.id)} style={{ float: 'right' }}>X</button>
-            </li>)}
-        </ul>;
-    }
 }

+ 6 - 2
src/mol-plugin/ui/plugin.tsx

@@ -8,10 +8,12 @@ import * as React from 'react';
 import { PluginContext } from '../context';
 import { StateTree } from './state-tree';
 import { Viewport, ViewportControls } from './viewport';
-import { Controls, _test_UpdateTransform, _test_ApplyAction, _test_TrajectoryControls, CameraSnapshots } from './controls';
+import { Controls, _test_UpdateTransform, _test_ApplyAction, _test_TrajectoryControls } from './controls';
 import { PluginComponent, PluginReactContext } from './base';
 import { merge } from 'rxjs';
 import { State } from 'mol-state';
+import { CameraSnapshots } from './camera';
+import { StateSnapshots } from './state';
 
 export class Plugin extends React.Component<{ plugin: PluginContext }, {}> {
     render() {
@@ -29,12 +31,14 @@ export class Plugin extends React.Component<{ plugin: PluginContext }, {}> {
                     </div>
                     <ViewportControls />
                 </div>
-                <div style={{ position: 'absolute', width: '300px', right: '0', height: '100%', padding: '10px' }}>
+                <div style={{ position: 'absolute', width: '300px', right: '0', height: '100%', padding: '10px', overflowY: 'scroll' }}>
                     <_test_CurrentObject />
                     <hr />
                     <Controls />
                     <hr />
                     <CameraSnapshots />
+                    <hr />
+                    <StateSnapshots />
                 </div>
             </div>
         </PluginReactContext.Provider>;

+ 67 - 0
src/mol-plugin/ui/state.tsx

@@ -0,0 +1,67 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+import { PluginCommands } from 'mol-plugin/command';
+import * as React from 'react';
+import { PluginComponent } from './base';
+
+export class StateSnapshots extends PluginComponent<{ }, { }> {
+    render() {
+        return <div>
+            <h3>State Snapshots</h3>
+            <StateSnapshotControls />
+            <StateSnapshotList />
+        </div>;
+    }
+}
+
+class StateSnapshotControls extends PluginComponent<{ }, { name: string, description: string }> {
+    state = { name: '', description: '' };
+
+    add = () => {
+        PluginCommands.State.Snapshots.Add.dispatch(this.plugin, this.state);
+        this.setState({ name: '', description: '' })
+    }
+
+    clear = () => {
+        PluginCommands.State.Snapshots.Clear.dispatch(this.plugin, {});
+    }
+
+    render() {
+        return <div>
+            <input type='text' value={this.state.name} placeholder='Name...' style={{ width: '33%', display: 'block', float: 'left' }} onChange={e => this.setState({ name: e.target.value })} />
+            <input type='text' value={this.state.description} placeholder='Description...' style={{ width: '67%', display: 'block' }} onChange={e => this.setState({ description: e.target.value })} />
+            <button style={{ float: 'right' }} onClick={this.clear}>Clear</button>
+            <button onClick={this.add}>Add</button>
+        </div>;
+    }
+}
+
+class StateSnapshotList extends PluginComponent<{ }, { }> {
+    componentDidMount() {
+        this.subscribe(this.plugin.events.state.snapshots.changed, () => this.forceUpdate());
+    }
+
+    apply(id: string) {
+        return () => PluginCommands.State.Snapshots.Apply.dispatch(this.plugin, { id });
+    }
+
+    remove(id: string) {
+        return () => {
+            PluginCommands.State.Snapshots.Remove.dispatch(this.plugin, { id });
+        }
+    }
+
+    render() {
+        return <ul style={{ listStyle: 'none' }}>
+            {this.plugin.state.snapshots.entries.valueSeq().map(e =><li key={e!.id}>
+                <button onClick={this.apply(e!.id)}>Set</button>
+                &nbsp;{e!.name} <small>{e!.description}</small>
+                <button onClick={this.remove(e!.id)} style={{ float: 'right' }}>X</button>
+            </li>)}
+        </ul>;
+    }
+}