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

state file export improvements

- save full state including all snapshots instead of just the current one
parent c475cb29
No related branches found
No related tags found
No related merge requests found
...@@ -129,7 +129,7 @@ class PluginStateSnapshotManager extends StatefulPluginComponent<{ ...@@ -129,7 +129,7 @@ class PluginStateSnapshotManager extends StatefulPluginComponent<{
return this.state.entries.get(idx).snapshot.id; return this.state.entries.get(idx).snapshot.id;
} }
async setRemoteSnapshot(snapshot: PluginStateSnapshotManager.RemoteSnapshot): Promise<PluginState.Snapshot | undefined> { async setStateSnapshot(snapshot: PluginStateSnapshotManager.StateSnapshot): Promise<PluginState.Snapshot | undefined> {
this.clear(); this.clear();
const entries = List<PluginStateSnapshotManager.Entry>().asMutable(); const entries = List<PluginStateSnapshotManager.Entry>().asMutable();
for (const e of snapshot.entries) { for (const e of snapshot.entries) {
...@@ -157,8 +157,21 @@ class PluginStateSnapshotManager extends StatefulPluginComponent<{ ...@@ -157,8 +157,21 @@ class PluginStateSnapshotManager extends StatefulPluginComponent<{
return next; return next;
} }
getRemoteSnapshot(options?: { name?: string, description?: string, playOnLoad?: boolean }): PluginStateSnapshotManager.RemoteSnapshot { private syncCurrent(options?: { name?: string, description?: string, params?: PluginState.GetSnapshotParams }) {
const snapshot = this.plugin.state.getSnapshot(options?.params);
if (this.state.entries.size === 0 || !this.state.current) {
this.add(PluginStateSnapshotManager.Entry(snapshot, { name: options?.name, description: options?.description }));
} else {
this.replace(this.state.current, snapshot);
}
}
getStateSnapshot(options?: { name?: string, description?: string, playOnLoad?: boolean, params?: PluginState.GetSnapshotParams }): PluginStateSnapshotManager.StateSnapshot {
// TODO: diffing and all that fancy stuff // TODO: diffing and all that fancy stuff
// TODO: the options need to be handled better, particularky options.params
this.syncCurrent(options);
return { return {
timestamp: +new Date(), timestamp: +new Date(),
name: options && options.name, name: options && options.name,
...@@ -173,7 +186,7 @@ class PluginStateSnapshotManager extends StatefulPluginComponent<{ ...@@ -173,7 +186,7 @@ class PluginStateSnapshotManager extends StatefulPluginComponent<{
} }
async serialize(type: 'json' | 'zip' = 'json') { async serialize(type: 'json' | 'zip' = 'json') {
const json = JSON.stringify(this.plugin.state.getSnapshot(), null, 2); const json = JSON.stringify(this.getStateSnapshot(), null, 2);
if (type === 'json') { if (type === 'json') {
return new Blob([json], {type : 'application/json;charset=utf-8'}); return new Blob([json], {type : 'application/json;charset=utf-8'});
...@@ -234,7 +247,7 @@ class PluginStateSnapshotManager extends StatefulPluginComponent<{ ...@@ -234,7 +247,7 @@ class PluginStateSnapshotManager extends StatefulPluginComponent<{
} }
const snapshot = JSON.parse(stateData); const snapshot = JSON.parse(stateData);
return this.plugin.state.setSnapshot(snapshot); return this.setStateSnapshot(snapshot);
} }
} catch (e) { } catch (e) {
this.plugin.log.error(`Reading state: ${e}`); this.plugin.log.error(`Reading state: ${e}`);
...@@ -312,7 +325,7 @@ namespace PluginStateSnapshotManager { ...@@ -312,7 +325,7 @@ namespace PluginStateSnapshotManager {
return { timestamp: +new Date(), snapshot, ...params }; return { timestamp: +new Date(), snapshot, ...params };
} }
export interface RemoteSnapshot { export interface StateSnapshot {
timestamp: number, timestamp: number,
name?: string, name?: string,
description?: string, description?: string,
......
...@@ -124,7 +124,6 @@ export class StructureFocusManager extends StatefulPluginComponent<StructureFocu ...@@ -124,7 +124,6 @@ export class StructureFocusManager extends StatefulPluginComponent<StructureFocu
} }
setSnapshot(snapshot: StructureFocusSnapshot) { setSnapshot(snapshot: StructureFocusSnapshot) {
console.log(snapshot);
if (!snapshot.current) return; if (!snapshot.current) return;
const { label, ref, bundle, category } = snapshot.current; const { label, ref, bundle, category } = snapshot.current;
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
* @author David Sehnal <david.sehnal@gmail.com> * @author David Sehnal <david.sehnal@gmail.com>
*/ */
import { ArrowDownward, ArrowUpward, CloudUpload, DeleteOutlined, SwapHoriz, SaveOutlined, GetApp } from '@material-ui/icons'; import { ArrowDownward, ArrowUpward, CloudUpload, DeleteOutlined, GetApp, OpenInBrowser, SaveOutlined, SwapHoriz } from '@material-ui/icons';
import { OrderedMap } from 'immutable'; import { OrderedMap } from 'immutable';
import * as React from 'react'; import * as React from 'react';
import { PluginCommands } from '../../mol-plugin/commands'; import { PluginCommands } from '../../mol-plugin/commands';
...@@ -16,6 +16,7 @@ import { ParamDefinition as PD } from '../../mol-util/param-definition'; ...@@ -16,6 +16,7 @@ import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { urlCombine } from '../../mol-util/url'; import { urlCombine } from '../../mol-util/url';
import { PluginUIComponent, PurePluginUIComponent } from '../base'; import { PluginUIComponent, PurePluginUIComponent } from '../base';
import { Button, IconButton, SectionHeader } from '../controls/common'; import { Button, IconButton, SectionHeader } from '../controls/common';
import { Icon } from '../controls/icons';
import { ParameterControls } from '../controls/parameters'; import { ParameterControls } from '../controls/parameters';
export class StateSnapshots extends PluginUIComponent<{}> { export class StateSnapshots extends PluginUIComponent<{}> {
...@@ -50,10 +51,14 @@ export class StateExportImportControls extends PluginUIComponent { ...@@ -50,10 +51,14 @@ export class StateExportImportControls extends PluginUIComponent {
render() { render() {
return <div className='msp-flex-row'> return <div className='msp-flex-row'>
<Button icon={GetApp} onClick={this.downloadToFileJson} title='Save state description'>Base</Button> <Button icon={GetApp} onClick={this.downloadToFileJson} title='Save the state description. Input data are loaded using the provided sources. Does not work if local files are used as input.'>
<Button icon={GetApp} onClick={this.downloadToFileZip} title='Save state including the data as zip.'>All</Button> Base
</Button>
<Button icon={GetApp} onClick={this.downloadToFileZip} title='Save the state including the input data.'>
Full
</Button>
<div className='msp-btn msp-btn-block msp-btn-action msp-loader-msp-btn-file'> <div className='msp-btn msp-btn-block msp-btn-action msp-loader-msp-btn-file'>
{'Open'} <input onChange={this.open} type='file' multiple={false} accept='.json,.zip' /> <Icon svg={OpenInBrowser} inline /> Open <input onChange={this.open} type='file' multiple={false} accept='.json,.zip' />
</div> </div>
</div>; </div>;
} }
...@@ -152,20 +157,19 @@ class LocalStateSnapshotList extends PluginUIComponent<{}, {}> { ...@@ -152,20 +157,19 @@ class LocalStateSnapshotList extends PluginUIComponent<{}, {}> {
render() { render() {
const current = this.plugin.managers.snapshot.state.current; const current = this.plugin.managers.snapshot.state.current;
return <ul style={{ listStyle: 'none' }} className='msp-state-list'> return <ul style={{ listStyle: 'none', marginTop: '1px' }} className='msp-state-list'>
{this.plugin.managers.snapshot.state.entries.map(e => <li key={e!.snapshot.id}> {this.plugin.managers.snapshot.state.entries.map(e => <li key={e!.snapshot.id} className='msp-flex-row'>
<Button data-id={e!.snapshot.id} onClick={this.apply}> <Button data-id={e!.snapshot.id} onClick={this.apply} className='msp-no-overflow'>
{(console.log(e!.snapshot.durationInMs), false)}
<span style={{ fontWeight: e!.snapshot.id === current ? 'bold' : void 0 }}> <span style={{ fontWeight: e!.snapshot.id === current ? 'bold' : void 0 }}>
{e!.name || new Date(e!.timestamp).toLocaleString()}</span> <small> {e!.name || new Date(e!.timestamp).toLocaleString()}</span> <small>
{`${e!.snapshot.durationInMs ? formatTimespan(e!.snapshot.durationInMs, false) + `${e!.description ? ', ' : ''}` : ''}${e!.description ? e!.description : ''}`} {`${e!.snapshot.durationInMs ? formatTimespan(e!.snapshot.durationInMs, false) + `${e!.description ? ', ' : ''}` : ''}${e!.description ? e!.description : ''}`}
</small> </small>
</Button> </Button>
<div> <IconButton svg={ArrowUpward} data-id={e!.snapshot.id} title='Move Up' onClick={this.moveUp} flex='20px' />
<IconButton svg={ArrowUpward} data-id={e!.snapshot.id} title='Move Up' onClick={this.moveUp} small={true} /> <IconButton svg={ArrowDownward} data-id={e!.snapshot.id} title='Move Down' onClick={this.moveDown} flex='20px' />
<IconButton svg={ArrowDownward} data-id={e!.snapshot.id} title='Move Down' onClick={this.moveDown} small={true} /> <IconButton svg={SwapHoriz} data-id={e!.snapshot.id} title='Replace' onClick={this.replace} flex='20px' />
<IconButton svg={SwapHoriz} data-id={e!.snapshot.id} title='Replace' onClick={this.replace} small={true} /> <IconButton svg={DeleteOutlined} data-id={e!.snapshot.id} title='Remove' onClick={this.remove} flex='20px' />
<IconButton svg={DeleteOutlined} data-id={e!.snapshot.id} title='Remove' onClick={this.remove} small={true} />
</div>
</li>)} </li>)}
</ul>; </ul>;
} }
...@@ -235,20 +239,14 @@ export class RemoteStateSnapshots extends PluginUIComponent< ...@@ -235,20 +239,14 @@ export class RemoteStateSnapshots extends PluginUIComponent<
this.setState({ isBusy: true }); this.setState({ isBusy: true });
this.plugin.config.set(PluginConfig.State.CurrentServer, this.state.params.options.serverUrl); this.plugin.config.set(PluginConfig.State.CurrentServer, this.state.params.options.serverUrl);
if (this.plugin.managers.snapshot.state.entries.size === 0) {
await PluginCommands.State.Snapshots.Add(this.plugin, {
name: this.state.params.name,
description: this.state.params.options.description,
params: this.plugin.managers.snapshot.currentGetSnapshotParams
});
}
await PluginCommands.State.Snapshots.Upload(this.plugin, { await PluginCommands.State.Snapshots.Upload(this.plugin, {
name: this.state.params.name, name: this.state.params.name,
description: this.state.params.options.description, description: this.state.params.options.description,
playOnLoad: this.state.params.options.playOnLoad, playOnLoad: this.state.params.options.playOnLoad,
serverUrl: this.state.params.options.serverUrl serverUrl: this.state.params.options.serverUrl,
params: this.plugin.managers.snapshot.currentGetSnapshotParams
}); });
this.setState({ isBusy: false }); this.setState({ isBusy: false });
this.plugin.log.message('Snapshot uploaded.'); this.plugin.log.message('Snapshot uploaded.');
this.refresh(); this.refresh();
......
...@@ -145,10 +145,10 @@ export class DownloadScreenshotControls extends PluginUIComponent<{ close: () => ...@@ -145,10 +145,10 @@ export class DownloadScreenshotControls extends PluginUIComponent<{ close: () =>
<Button icon={Launch} onClick={this.openTab} disabled={this.state.isDisabled}>Open in new Tab</Button> <Button icon={Launch} onClick={this.openTab} disabled={this.state.isDisabled}>Open in new Tab</Button>
</div> </div>
<ParameterControls params={this.plugin.helpers.viewportScreenshot!.params} values={this.plugin.helpers.viewportScreenshot!.values} onChange={this.setProps} isDisabled={this.state.isDisabled} /> <ParameterControls params={this.plugin.helpers.viewportScreenshot!.params} values={this.plugin.helpers.viewportScreenshot!.values} onChange={this.setProps} isDisabled={this.state.isDisabled} />
<ExpandGroup header='State Snapshot' accent> <ExpandGroup header='State Snapshot'>
<StateExportImportControls /> <StateExportImportControls />
<div className='msp-help-description' style={{ padding: '10px'}}> <div className='msp-help-text' style={{ padding: '10px'}}>
<Icon svg={Warning} /> This is an experimental feature and states stored today might not be openable in an upcoming version. <Icon svg={Warning} /> This is an experimental feature and stored states might not be openable in a future version.
</div> </div>
</ExpandGroup> </ExpandGroup>
</div>; </div>;
......
...@@ -163,19 +163,19 @@ export function Snapshots(ctx: PluginContext) { ...@@ -163,19 +163,19 @@ export function Snapshots(ctx: PluginContext) {
return ctx.state.setSnapshot(snapshot); return ctx.state.setSnapshot(snapshot);
}); });
PluginCommands.State.Snapshots.Upload.subscribe(ctx, ({ name, description, playOnLoad, serverUrl }) => { PluginCommands.State.Snapshots.Upload.subscribe(ctx, ({ name, description, playOnLoad, serverUrl, params }) => {
return fetch(urlCombine(serverUrl, `set?name=${encodeURIComponent(name || '')}&description=${encodeURIComponent(description || '')}`), { return fetch(urlCombine(serverUrl, `set?name=${encodeURIComponent(name || '')}&description=${encodeURIComponent(description || '')}`), {
method: 'POST', method: 'POST',
mode: 'cors', mode: 'cors',
referrer: 'no-referrer', referrer: 'no-referrer',
headers: { 'Content-Type': 'application/json; charset=utf-8' }, headers: { 'Content-Type': 'application/json; charset=utf-8' },
body: JSON.stringify(ctx.managers.snapshot.getRemoteSnapshot({ name, description, playOnLoad })) body: JSON.stringify(ctx.managers.snapshot.getStateSnapshot({ name, description, playOnLoad, params }))
}) as any as Promise<void>; }) as any as Promise<void>;
}); });
PluginCommands.State.Snapshots.Fetch.subscribe(ctx, async ({ url }) => { PluginCommands.State.Snapshots.Fetch.subscribe(ctx, async ({ url }) => {
const json = await ctx.runTask(ctx.fetch({ url, type: 'json' })); // fetch(url, { referrer: 'no-referrer' }); const json = await ctx.runTask(ctx.fetch({ url, type: 'json' })); // fetch(url, { referrer: 'no-referrer' });
await ctx.managers.snapshot.setRemoteSnapshot(json.data); await ctx.managers.snapshot.setStateSnapshot(json.data);
}); });
PluginCommands.State.Snapshots.DownloadToFile.subscribe(ctx, async ({ name, type }) => { PluginCommands.State.Snapshots.DownloadToFile.subscribe(ctx, async ({ name, type }) => {
......
...@@ -34,7 +34,7 @@ export const PluginCommands = { ...@@ -34,7 +34,7 @@ export const PluginCommands = {
Apply: PluginCommand<{ id: string }>(), Apply: PluginCommand<{ id: string }>(),
Clear: PluginCommand<{}>(), Clear: PluginCommand<{}>(),
Upload: PluginCommand<{ name?: string, description?: string, playOnLoad?: boolean, serverUrl: string }>(), Upload: PluginCommand<{ name?: string, description?: string, playOnLoad?: boolean, serverUrl: string, params?: PluginState.GetSnapshotParams }>(),
Fetch: PluginCommand<{ url: string }>(), Fetch: PluginCommand<{ url: string }>(),
DownloadToFile: PluginCommand<{ name?: string, type: 'json' | 'zip' }>(), DownloadToFile: PluginCommand<{ name?: string, type: 'json' | 'zip' }>(),
......
...@@ -56,7 +56,7 @@ class PluginState { ...@@ -56,7 +56,7 @@ class PluginState {
canvas3d: p.canvas3d ? { props: this.plugin.canvas3d?.props } : void 0, canvas3d: p.canvas3d ? { props: this.plugin.canvas3d?.props } : void 0,
interactivity: p.interactivity ? { props: this.plugin.managers.interactivity.props } : void 0, interactivity: p.interactivity ? { props: this.plugin.managers.interactivity.props } : void 0,
structureFocus: this.plugin.managers.structure.focus.getSnapshot(), structureFocus: this.plugin.managers.structure.focus.getSnapshot(),
durationInMs: params && params.durationInMs durationInMs: p?.durationInMs
}; };
} }
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment