Skip to content
Snippets Groups Projects
Commit ebca12ee authored by David Sehnal's avatar David Sehnal
Browse files

mol-plugin: Remote state store prototype

parent b3afb8b5
No related branches found
No related tags found
No related merge requests found
...@@ -72,4 +72,20 @@ export function Snapshots(ctx: PluginContext) { ...@@ -72,4 +72,20 @@ export function Snapshots(ctx: PluginContext) {
const e = ctx.state.snapshots.getEntry(id); const e = ctx.state.snapshots.getEntry(id);
return ctx.state.setSnapshot(e.snapshot); 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);
});
} }
\ No newline at end of file
...@@ -25,4 +25,7 @@ export const Snapshots = { ...@@ -25,4 +25,7 @@ export const Snapshots = {
Remove: PluginCommand<{ id: string }>({ isImmediate: true }), Remove: PluginCommand<{ id: string }>({ isImmediate: true }),
Apply: PluginCommand<{ id: string }>({ isImmediate: true }), Apply: PluginCommand<{ id: string }>({ isImmediate: true }),
Clear: PluginCommand<{ }>({ isImmediate: true }), Clear: PluginCommand<{ }>({ isImmediate: true }),
Upload: PluginCommand<{ name?: string, description?: string, serverUrl: string }>({ isImmediate: true }),
Fetch: PluginCommand<{ url: string }>()
} }
\ No newline at end of file
...@@ -8,6 +8,7 @@ import { PluginContext } from './context'; ...@@ -8,6 +8,7 @@ import { PluginContext } from './context';
import { Plugin } from './ui/plugin' import { Plugin } from './ui/plugin'
import * as React from 'react'; import * as React from 'react';
import * as ReactDOM from 'react-dom'; import * as ReactDOM from 'react-dom';
import { PluginCommands } from './command';
function getParam(name: string, regex: string): string { function getParam(name: string, regex: string): string {
let r = new RegExp(`${name}=(${regex})[&]?`, 'i'); let r = new RegExp(`${name}=(${regex})[&]?`, 'i');
...@@ -28,8 +29,9 @@ export function createPlugin(target: HTMLElement): PluginContext { ...@@ -28,8 +29,9 @@ export function createPlugin(target: HTMLElement): PluginContext {
} }
function trySetSnapshot(ctx: PluginContext) { function trySetSnapshot(ctx: PluginContext) {
const snapshot = getParam('snapshot', `(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?`); const snapshotUrl = getParam('snapshot-url', `[^&]+`);
if (!snapshot) return; if (!snapshotUrl) return;
const data = JSON.parse(atob(snapshot)); // const data = JSON.parse(atob(snapshot));
setTimeout(() => ctx.state.setSnapshot(data), 250); // setTimeout(() => ctx.state.setSnapshot(data), 250);
PluginCommands.State.Snapshots.Fetch.dispatch(ctx, { url: snapshotUrl })
} }
\ No newline at end of file
...@@ -38,7 +38,7 @@ export class Plugin extends React.Component<{ plugin: PluginContext }, {}> { ...@@ -38,7 +38,7 @@ export class Plugin extends React.Component<{ plugin: PluginContext }, {}> {
<BackgroundTaskProgress /> <BackgroundTaskProgress />
</div> </div>
</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 /> <CurrentObject />
<hr /> <hr />
<Controls /> <Controls />
......
...@@ -7,22 +7,31 @@ ...@@ -7,22 +7,31 @@
import { PluginCommands } from 'mol-plugin/command'; import { PluginCommands } from 'mol-plugin/command';
import * as React from 'react'; import * as React from 'react';
import { PluginComponent } from './base'; 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() { render() {
return <div> return <div>
<h3>State Snapshots</h3> <h3>State Snapshots</h3>
<StateSnapshotControls /> <StateSnapshotControls serverUrl={this.state.serverUrl} serverChanged={this.updateServerUrl} />
<StateSnapshotList /> <b>Local</b>
<LocalStateSnapshotList />
<RemoteStateSnapshotList serverUrl={this.state.serverUrl} />
</div>; </div>;
} }
} }
class StateSnapshotControls extends PluginComponent<{ }, { name: string, description: string }> { class StateSnapshotControls extends PluginComponent<{ serverUrl: string, serverChanged: (url: string) => void }, { name: string, description: string, serverUrl: string, isUploading: boolean }> {
state = { name: '', description: '' }; state = { name: '', description: '', serverUrl: this.props.serverUrl, isUploading: false };
add = () => { 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: '' }) this.setState({ name: '', description: '' })
} }
...@@ -30,17 +39,32 @@ class StateSnapshotControls extends PluginComponent<{ }, { name: string, descrip ...@@ -30,17 +39,32 @@ class StateSnapshotControls extends PluginComponent<{ }, { name: string, descrip
PluginCommands.State.Snapshots.Clear.dispatch(this.plugin, {}); 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() { render() {
return <div> 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.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.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 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>; </div>;
} }
} }
class StateSnapshotList extends PluginComponent<{ }, { }> { class LocalStateSnapshotList extends PluginComponent<{ }, { }> {
componentDidMount() { componentDidMount() {
this.subscribe(this.plugin.events.state.snapshots.changed, () => this.forceUpdate()); this.subscribe(this.plugin.events.state.snapshots.changed, () => this.forceUpdate());
} }
...@@ -64,4 +88,42 @@ class StateSnapshotList extends PluginComponent<{ }, { }> { ...@@ -64,4 +88,42 @@ class StateSnapshotList extends PluginComponent<{ }, { }> {
</li>)} </li>)}
</ul>; </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>;
}
} }
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment