Browse Source

mol-plugin: Remote state store prototype

David Sehnal 6 years ago
parent
commit
ebca12ee1c

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

@@ -72,4 +72,20 @@ export function Snapshots(ctx: PluginContext) {
         const e = ctx.state.snapshots.getEntry(id);
         return ctx.state.setSnapshot(e.snapshot);
     });
+
+    PluginCommands.State.Snapshots.Upload.subscribe(ctx, ({ name, description, serverUrl }) => {
+        return fetch(`${serverUrl}/set?name=${encodeURIComponent(name || '')}&description=${encodeURIComponent(description || '')}`, {
+            method: 'POST',
+            mode: 'cors',
+            referrer: 'no-referrer',
+            headers: { 'Content-Type': 'application/json; charset=utf-8' },
+            body: JSON.stringify(ctx.state.getSnapshot())
+        }) as any as Promise<void>;
+    });
+
+    PluginCommands.State.Snapshots.Fetch.subscribe(ctx, async ({ url }) => {
+        const req = await fetch(url, { referrer: 'no-referrer' });
+        const json = await req.json();
+        return ctx.state.setSnapshot(json.data);
+    });
 }

+ 3 - 0
src/mol-plugin/command/state.ts

@@ -25,4 +25,7 @@ export const Snapshots = {
     Remove: PluginCommand<{ id: string }>({ isImmediate: true }),
     Apply: PluginCommand<{ id: string }>({ isImmediate: true }),
     Clear: PluginCommand<{ }>({ isImmediate: true }),
+
+    Upload: PluginCommand<{ name?: string, description?: string, serverUrl: string }>({ isImmediate: true }),
+    Fetch: PluginCommand<{ url: string }>()
 }

+ 6 - 4
src/mol-plugin/index.ts

@@ -8,6 +8,7 @@ import { PluginContext } from './context';
 import { Plugin } from './ui/plugin'
 import * as React from 'react';
 import * as ReactDOM from 'react-dom';
+import { PluginCommands } from './command';
 
 function getParam(name: string, regex: string): string {
     let r = new RegExp(`${name}=(${regex})[&]?`, 'i');
@@ -28,8 +29,9 @@ export function createPlugin(target: HTMLElement): PluginContext {
 }
 
 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);
+    const snapshotUrl = getParam('snapshot-url', `[^&]+`);
+    if (!snapshotUrl) return;
+    // const data = JSON.parse(atob(snapshot));
+    // setTimeout(() => ctx.state.setSnapshot(data), 250);
+    PluginCommands.State.Snapshots.Fetch.dispatch(ctx, { url: snapshotUrl })
 }

+ 1 - 1
src/mol-plugin/ui/plugin.tsx

@@ -38,7 +38,7 @@ export class Plugin extends React.Component<{ plugin: PluginContext }, {}> {
                         <BackgroundTaskProgress />
                     </div>
                 </div>
-                <div style={{ position: 'absolute', width: '300px', right: '0', top: '0', padding: '10px', overflowY: 'scroll' }}>
+                <div style={{ position: 'absolute', width: '300px', right: '0', top: '0', bottom: '0', padding: '10px', overflowY: 'scroll' }}>
                     <CurrentObject />
                     <hr />
                     <Controls />

+ 70 - 8
src/mol-plugin/ui/state.tsx

@@ -7,22 +7,31 @@
 import { PluginCommands } from 'mol-plugin/command';
 import * as React from 'react';
 import { PluginComponent } from './base';
+import { shallowEqual } from 'mol-util';
+import { List } from 'immutable';
+import { LogEntry } from 'mol-util/log-entry';
+
+export class StateSnapshots extends PluginComponent<{ }, { serverUrl: string }> {
+    state = { serverUrl: 'http://webchem.ncbr.muni.cz/molstar-state' }
+
+    updateServerUrl = (serverUrl: string) => { this.setState({ serverUrl }) };
 
-export class StateSnapshots extends PluginComponent<{ }, { }> {
     render() {
         return <div>
             <h3>State Snapshots</h3>
-            <StateSnapshotControls />
-            <StateSnapshotList />
+            <StateSnapshotControls serverUrl={this.state.serverUrl} serverChanged={this.updateServerUrl} />
+            <b>Local</b>
+            <LocalStateSnapshotList />
+            <RemoteStateSnapshotList serverUrl={this.state.serverUrl} />
         </div>;
     }
 }
 
-class StateSnapshotControls extends PluginComponent<{ }, { name: string, description: string }> {
-    state = { name: '', description: '' };
+class StateSnapshotControls extends PluginComponent<{ serverUrl: string, serverChanged: (url: string) => void }, { name: string, description: string, serverUrl: string, isUploading: boolean }> {
+    state = { name: '', description: '', serverUrl: this.props.serverUrl, isUploading: false };
 
     add = () => {
-        PluginCommands.State.Snapshots.Add.dispatch(this.plugin, this.state);
+        PluginCommands.State.Snapshots.Add.dispatch(this.plugin, { name: this.state.name, description: this.state.description });
         this.setState({ name: '', description: '' })
     }
 
@@ -30,17 +39,32 @@ class StateSnapshotControls extends PluginComponent<{ }, { name: string, descrip
         PluginCommands.State.Snapshots.Clear.dispatch(this.plugin, {});
     }
 
+    shouldComponentUpdate(nextProps: { serverUrl: string, serverChanged: (url: string) => void }, nextState: { name: string, description: string, serverUrl: string, isUploading: boolean }) {
+        return !shallowEqual(this.props, nextProps) || !shallowEqual(this.state, nextState);
+    }
+
+    upload = async () => {
+        this.setState({ isUploading: true });
+        await PluginCommands.State.Snapshots.Upload.dispatch(this.plugin, { name: this.state.name, description: this.state.description, serverUrl: this.state.serverUrl });
+        this.setState({ isUploading: false });
+    }
+
     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 })} />
+            <input type='text' value={this.state.serverUrl} placeholder='Server URL...' style={{ width: '100%', display: 'block' }} onChange={e => {
+                this.setState({ serverUrl: e.target.value });
+                this.props.serverChanged(e.target.value);
+            }} />
             <button style={{ float: 'right' }} onClick={this.clear}>Clear</button>
-            <button onClick={this.add}>Add</button>
+            <button onClick={this.add}>Add Local</button>
+            <button onClick={this.upload} disabled={this.state.isUploading}>Upload</button>
         </div>;
     }
 }
 
-class StateSnapshotList extends PluginComponent<{ }, { }> {
+class LocalStateSnapshotList extends PluginComponent<{ }, { }> {
     componentDidMount() {
         this.subscribe(this.plugin.events.state.snapshots.changed, () => this.forceUpdate());
     }
@@ -64,4 +88,42 @@ class StateSnapshotList extends PluginComponent<{ }, { }> {
             </li>)}
         </ul>;
     }
+}
+
+type RemoteEntry = { url: string, timestamp: number, id: string, name: string, description: string }
+class RemoteStateSnapshotList extends PluginComponent<{ serverUrl: string }, { entries: List<RemoteEntry>, isFetching: boolean }> {
+    state = { entries: List<RemoteEntry>(), isFetching: false };
+
+    componentDidMount() {
+        this.subscribe(this.plugin.events.state.snapshots.changed, () => this.forceUpdate());
+        this.refresh();
+    }
+
+    refresh = async () => {
+        try {
+            this.setState({ isFetching: true });
+            const req = await fetch(`${this.props.serverUrl}/list`);
+            const json: RemoteEntry[] = await req.json();
+            this.setState({ entries: List<RemoteEntry>(json.map((e: RemoteEntry) => ({ ...e, url: `${this.props.serverUrl}/get/${e.id}` }))), isFetching: false })
+        } catch (e) {
+            this.plugin.log(LogEntry.error('Fetching Remote Snapshots: ' + e));
+            this.setState({ entries: List<RemoteEntry>(), isFetching: false })
+        }
+    }
+
+    fetch(url: string) {
+        return () => PluginCommands.State.Snapshots.Fetch.dispatch(this.plugin, { url });
+    }
+
+    render() {
+        return <div>
+            <b>Remote</b> <button onClick={this.refresh} disabled={this.state.isFetching}>Refresh</button>
+            <ul style={{ listStyle: 'none' }}>
+                {this.state.entries.valueSeq().map(e =><li key={e!.id}>
+                    <button onClick={this.fetch(e!.url)} disabled={this.state.isFetching}>Fetch</button>
+                    &nbsp;{e!.name} <small>{e!.description}</small>
+                </li>)}
+            </ul>
+        </div>;
+    }
 }