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) {
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);
});
}
\ No newline at end of file
......@@ -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 }>()
}
\ No newline at end of file
......@@ -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 })
}
\ No newline at end of file
......@@ -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 />
......
......@@ -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>;
}
}
\ 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