-
David Sehnal authoredDavid Sehnal authored
state.tsx 7.50 KiB
/**
* 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';
import { shallowEqual } from 'mol-util';
import { List } from 'immutable';
import { ParameterControls } from './controls/parameters';
import { ParamDefinition as PD} from 'mol-util/param-definition';
import { Subject } from 'rxjs';
export class StateSnapshots extends PluginComponent<{ }, { serverUrl: string }> {
state = { serverUrl: 'https://webchem.ncbr.muni.cz/molstar-state' }
updateServerUrl = (serverUrl: string) => { this.setState({ serverUrl }) };
render() {
return <div>
<div className='msp-section-header'>State Snapshots</div>
<StateSnapshotControls serverUrl={this.state.serverUrl} serverChanged={this.updateServerUrl} />
<LocalStateSnapshotList />
<RemoteStateSnapshotList serverUrl={this.state.serverUrl} />
</div>;
}
}
// TODO: this is not nice: device some custom event system.
const UploadedEvent = new Subject();
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 };
static Params = {
name: PD.Text(),
description: PD.Text(),
serverUrl: PD.Text()
}
add = () => {
PluginCommands.State.Snapshots.Add.dispatch(this.plugin, { name: this.state.name, description: this.state.description });
this.setState({ name: '', description: '' })
}
clear = () => {
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 });
this.plugin.log.message('Snapshot uploaded.');
UploadedEvent.next();
}
download = () => {
PluginCommands.State.Snapshots.DownloadToFile.dispatch(this.plugin, { name: this.state.name });
}
open = (e: React.ChangeEvent<HTMLInputElement>) => {
if (!e.target.files || !e.target.files![0]) return;
PluginCommands.State.Snapshots.OpenFile.dispatch(this.plugin, { file: e.target.files![0] });
}
render() {
return <div>
<ParameterControls params={StateSnapshotControls.Params} values={this.state} onEnter={this.add} onChange={p => {
this.setState({ [p.name]: p.value } as any);
if (p.name === 'serverUrl') this.props.serverChanged(p.value);
}}/>
<div className='msp-btn-row-group'>
<button className='msp-btn msp-btn-block msp-form-control' onClick={this.add}>Add Local</button>
<button className='msp-btn msp-btn-block msp-form-control' onClick={this.upload} disabled={this.state.isUploading}>Upload</button>
<button className='msp-btn msp-btn-block msp-form-control' onClick={this.clear}>Clear</button>
</div>
<div className='msp-btn-row-group'>
<button className='msp-btn msp-btn-block msp-form-control' onClick={this.download}>Download JSON</button>
<div className='msp-btn msp-btn-block msp-btn-action msp-loader-msp-btn-file'>
{'Open JSON'} <input onChange={this.open} type='file' multiple={false} accept='.json' />
</div>
</div>
</div>;
}
}
class LocalStateSnapshotList 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' }} className='msp-state-list'>
{this.plugin.state.snapshots.entries.valueSeq().map(e =><li key={e!.id}>
<button className='msp-btn msp-btn-block msp-form-control' onClick={this.apply(e!.id)}>{e!.name || e!.timestamp} <small>{e!.description}</small></button>
<button onClick={this.remove(e!.id)} className='msp-btn msp-btn-link msp-state-list-remove-button'>
<span className='msp-icon msp-icon-remove' />
</button>
</li>)}
</ul>;
}
}
type RemoteEntry = { url: string, removeUrl: 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();
this.subscribe(UploadedEvent, 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}`,
removeUrl: `${this.props.serverUrl}/remove/${e.id}`
}))),
isFetching: false })
} catch (e) {
this.plugin.log.error('Fetching Remote Snapshots: ' + e);
this.setState({ entries: List<RemoteEntry>(), isFetching: false })
}
}
fetch(url: string) {
return () => PluginCommands.State.Snapshots.Fetch.dispatch(this.plugin, { url });
}
remove(url: string) {
return async () => {
this.setState({ entries: List() });
try {
await fetch(url);
} catch { }
this.refresh();
}
}
render() {
return <div>
<button title='Click to Refresh' style={{fontWeight: 'bold'}} className='msp-btn msp-btn-block msp-form-control msp-section-header' onClick={this.refresh} disabled={this.state.isFetching}>↻ Remote Snapshots</button>
<ul style={{ listStyle: 'none' }} className='msp-state-list'>
{this.state.entries.valueSeq().map(e =><li key={e!.id}>
<button className='msp-btn msp-btn-block msp-form-control' onClick={this.fetch(e!.url)}>{e!.name || e!.timestamp} <small>{e!.description}</small></button>
<button onClick={this.remove(e!.removeUrl)} className='msp-btn msp-btn-link msp-state-list-remove-button'>
<span className='msp-icon msp-icon-remove' />
</button>
</li>)}
</ul>
</div>;
}
}