state.tsx 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. /**
  2. * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * @author David Sehnal <david.sehnal@gmail.com>
  5. */
  6. import { PluginCommands } from 'mol-plugin/command';
  7. import * as React from 'react';
  8. import { PluginComponent } from './base';
  9. import { shallowEqual } from 'mol-util';
  10. import { List } from 'immutable';
  11. import { LogEntry } from 'mol-util/log-entry';
  12. import { ParamDefinition as PD } from 'mol-util/param-definition';
  13. import { ParameterControls } from './controls/parameters';
  14. import { Subject } from 'rxjs';
  15. export class StateSnapshots extends PluginComponent<{ }, { serverUrl: string }> {
  16. state = { serverUrl: 'https://webchem.ncbr.muni.cz/molstar-state' }
  17. updateServerUrl = (serverUrl: string) => { this.setState({ serverUrl }) };
  18. render() {
  19. return <div>
  20. <div className='msp-section-header'>State Snapshots</div>
  21. <StateSnapshotControls serverUrl={this.state.serverUrl} serverChanged={this.updateServerUrl} />
  22. <LocalStateSnapshotList />
  23. <RemoteStateSnapshotList serverUrl={this.state.serverUrl} />
  24. </div>;
  25. }
  26. }
  27. // TODO: this is not nice: device some custom event system.
  28. const UploadedEvent = new Subject();
  29. class StateSnapshotControls extends PluginComponent<{ serverUrl: string, serverChanged: (url: string) => void }, { name: string, description: string, serverUrl: string, isUploading: boolean }> {
  30. state = { name: '', description: '', serverUrl: this.props.serverUrl, isUploading: false };
  31. static Params = {
  32. name: PD.Text(),
  33. description: PD.Text(),
  34. serverUrl: PD.Text()
  35. }
  36. add = () => {
  37. PluginCommands.State.Snapshots.Add.dispatch(this.plugin, { name: this.state.name, description: this.state.description });
  38. this.setState({ name: '', description: '' })
  39. }
  40. clear = () => {
  41. PluginCommands.State.Snapshots.Clear.dispatch(this.plugin, {});
  42. }
  43. shouldComponentUpdate(nextProps: { serverUrl: string, serverChanged: (url: string) => void }, nextState: { name: string, description: string, serverUrl: string, isUploading: boolean }) {
  44. return !shallowEqual(this.props, nextProps) || !shallowEqual(this.state, nextState);
  45. }
  46. upload = async () => {
  47. this.setState({ isUploading: true });
  48. await PluginCommands.State.Snapshots.Upload.dispatch(this.plugin, { name: this.state.name, description: this.state.description, serverUrl: this.state.serverUrl });
  49. this.setState({ isUploading: false });
  50. this.plugin.log(LogEntry.message('Snapshot uploaded.'));
  51. UploadedEvent.next();
  52. }
  53. render() {
  54. return <div>
  55. <ParameterControls params={StateSnapshotControls.Params} values={this.state} onEnter={this.add} onChange={p => {
  56. this.setState({ [p.name]: p.value } as any);
  57. if (p.name === 'serverUrl') this.props.serverChanged(p.value);
  58. }}/>
  59. <div className='msp-btn-row-group'>
  60. <button className='msp-btn msp-btn-block msp-form-control' onClick={this.add}>Add Local</button>
  61. <button className='msp-btn msp-btn-block msp-form-control' onClick={this.upload} disabled={this.state.isUploading}>Upload</button>
  62. <button className='msp-btn msp-btn-block msp-form-control' onClick={this.clear}>Clear</button>
  63. </div>
  64. </div>;
  65. }
  66. }
  67. class LocalStateSnapshotList extends PluginComponent<{ }, { }> {
  68. componentDidMount() {
  69. this.subscribe(this.plugin.events.state.snapshots.changed, () => this.forceUpdate());
  70. }
  71. apply(id: string) {
  72. return () => PluginCommands.State.Snapshots.Apply.dispatch(this.plugin, { id });
  73. }
  74. remove(id: string) {
  75. return () => {
  76. PluginCommands.State.Snapshots.Remove.dispatch(this.plugin, { id });
  77. }
  78. }
  79. render() {
  80. return <ul style={{ listStyle: 'none' }} className='msp-state-list'>
  81. {this.plugin.state.snapshots.entries.valueSeq().map(e =><li key={e!.id}>
  82. <button className='msp-btn msp-btn-block msp-form-control' onClick={this.apply(e!.id)}>{e!.name || e!.timestamp} <small>{e!.description}</small></button>
  83. <button onClick={this.remove(e!.id)} className='msp-btn msp-btn-link msp-state-list-remove-button'>
  84. <span className='msp-icon msp-icon-remove' />
  85. </button>
  86. </li>)}
  87. </ul>;
  88. }
  89. }
  90. type RemoteEntry = { url: string, removeUrl: string, timestamp: number, id: string, name: string, description: string }
  91. class RemoteStateSnapshotList extends PluginComponent<{ serverUrl: string }, { entries: List<RemoteEntry>, isFetching: boolean }> {
  92. state = { entries: List<RemoteEntry>(), isFetching: false };
  93. componentDidMount() {
  94. this.subscribe(this.plugin.events.state.snapshots.changed, () => this.forceUpdate());
  95. this.refresh();
  96. this.subscribe(UploadedEvent, this.refresh);
  97. }
  98. refresh = async () => {
  99. try {
  100. this.setState({ isFetching: true });
  101. const req = await fetch(`${this.props.serverUrl}/list`);
  102. const json: RemoteEntry[] = await req.json();
  103. this.setState({
  104. entries: List<RemoteEntry>(json.map((e: RemoteEntry) => ({
  105. ...e,
  106. url: `${this.props.serverUrl}/get/${e.id}`,
  107. removeUrl: `${this.props.serverUrl}/remove/${e.id}`
  108. }))),
  109. isFetching: false })
  110. } catch (e) {
  111. this.plugin.log(LogEntry.error('Fetching Remote Snapshots: ' + e));
  112. this.setState({ entries: List<RemoteEntry>(), isFetching: false })
  113. }
  114. }
  115. fetch(url: string) {
  116. return () => PluginCommands.State.Snapshots.Fetch.dispatch(this.plugin, { url });
  117. }
  118. remove(url: string) {
  119. return async () => {
  120. this.setState({ entries: List() });
  121. try {
  122. await fetch(url);
  123. } catch { }
  124. this.refresh();
  125. }
  126. }
  127. render() {
  128. return <div>
  129. <button title='Click to Refresh' style={{fontWeight: 'bold'}} className='msp-btn msp-btn-block msp-form-control' onClick={this.refresh} disabled={this.state.isFetching}>↻ Remote Snapshots</button>
  130. <ul style={{ listStyle: 'none' }} className='msp-state-list'>
  131. {this.state.entries.valueSeq().map(e =><li key={e!.id}>
  132. <button className='msp-btn msp-btn-block msp-form-control' onClick={this.fetch(e!.url)}>{e!.name || e!.timestamp} <small>{e!.description}</small></button>
  133. <button onClick={this.remove(e!.removeUrl)} className='msp-btn msp-btn-link msp-state-list-remove-button'>
  134. <span className='msp-icon msp-icon-remove' />
  135. </button>
  136. </li>)}
  137. </ul>
  138. </div>;
  139. }
  140. }