From 2429111a594a2e0ddb1f68bacc5822b7fdca155c Mon Sep 17 00:00:00 2001 From: David Sehnal <david.sehnal@gmail.com> Date: Fri, 17 Apr 2020 15:39:13 +0200 Subject: [PATCH] state file export improvements - save full state including all snapshots instead of just the current one --- src/mol-plugin-state/manager/snapshots.ts | 23 +++++++--- .../manager/structure/focus.ts | 1 - src/mol-plugin-ui/state/snapshots.tsx | 42 +++++++++---------- src/mol-plugin-ui/viewport/screenshot.tsx | 6 +-- src/mol-plugin/behavior/static/state.ts | 6 +-- src/mol-plugin/commands.ts | 2 +- src/mol-plugin/state.ts | 2 +- 7 files changed, 46 insertions(+), 36 deletions(-) diff --git a/src/mol-plugin-state/manager/snapshots.ts b/src/mol-plugin-state/manager/snapshots.ts index a5a6d449d..a84e1dc69 100644 --- a/src/mol-plugin-state/manager/snapshots.ts +++ b/src/mol-plugin-state/manager/snapshots.ts @@ -129,7 +129,7 @@ class PluginStateSnapshotManager extends StatefulPluginComponent<{ 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(); const entries = List<PluginStateSnapshotManager.Entry>().asMutable(); for (const e of snapshot.entries) { @@ -157,8 +157,21 @@ class PluginStateSnapshotManager extends StatefulPluginComponent<{ 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: the options need to be handled better, particularky options.params + this.syncCurrent(options); + return { timestamp: +new Date(), name: options && options.name, @@ -173,7 +186,7 @@ class PluginStateSnapshotManager extends StatefulPluginComponent<{ } 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') { return new Blob([json], {type : 'application/json;charset=utf-8'}); @@ -234,7 +247,7 @@ class PluginStateSnapshotManager extends StatefulPluginComponent<{ } const snapshot = JSON.parse(stateData); - return this.plugin.state.setSnapshot(snapshot); + return this.setStateSnapshot(snapshot); } } catch (e) { this.plugin.log.error(`Reading state: ${e}`); @@ -312,7 +325,7 @@ namespace PluginStateSnapshotManager { return { timestamp: +new Date(), snapshot, ...params }; } - export interface RemoteSnapshot { + export interface StateSnapshot { timestamp: number, name?: string, description?: string, diff --git a/src/mol-plugin-state/manager/structure/focus.ts b/src/mol-plugin-state/manager/structure/focus.ts index d86f4d204..5faa89021 100644 --- a/src/mol-plugin-state/manager/structure/focus.ts +++ b/src/mol-plugin-state/manager/structure/focus.ts @@ -124,7 +124,6 @@ export class StructureFocusManager extends StatefulPluginComponent<StructureFocu } setSnapshot(snapshot: StructureFocusSnapshot) { - console.log(snapshot); if (!snapshot.current) return; const { label, ref, bundle, category } = snapshot.current; diff --git a/src/mol-plugin-ui/state/snapshots.tsx b/src/mol-plugin-ui/state/snapshots.tsx index 08c6b7d7e..a1a296e86 100644 --- a/src/mol-plugin-ui/state/snapshots.tsx +++ b/src/mol-plugin-ui/state/snapshots.tsx @@ -4,7 +4,7 @@ * @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 * as React from 'react'; import { PluginCommands } from '../../mol-plugin/commands'; @@ -16,6 +16,7 @@ import { ParamDefinition as PD } from '../../mol-util/param-definition'; import { urlCombine } from '../../mol-util/url'; import { PluginUIComponent, PurePluginUIComponent } from '../base'; import { Button, IconButton, SectionHeader } from '../controls/common'; +import { Icon } from '../controls/icons'; import { ParameterControls } from '../controls/parameters'; export class StateSnapshots extends PluginUIComponent<{}> { @@ -50,10 +51,14 @@ export class StateExportImportControls extends PluginUIComponent { render() { return <div className='msp-flex-row'> - <Button icon={GetApp} onClick={this.downloadToFileJson} title='Save state description'>Base</Button> - <Button icon={GetApp} onClick={this.downloadToFileZip} title='Save state including the data as zip.'>All</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.'> + 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'> - {'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>; } @@ -152,20 +157,19 @@ class LocalStateSnapshotList extends PluginUIComponent<{}, {}> { render() { const current = this.plugin.managers.snapshot.state.current; - return <ul style={{ listStyle: 'none' }} className='msp-state-list'> - {this.plugin.managers.snapshot.state.entries.map(e => <li key={e!.snapshot.id}> - <Button data-id={e!.snapshot.id} onClick={this.apply}> + return <ul style={{ listStyle: 'none', marginTop: '1px' }} className='msp-state-list'> + {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} className='msp-no-overflow'> + {(console.log(e!.snapshot.durationInMs), false)} <span style={{ fontWeight: e!.snapshot.id === current ? 'bold' : void 0 }}> {e!.name || new Date(e!.timestamp).toLocaleString()}</span> <small> {`${e!.snapshot.durationInMs ? formatTimespan(e!.snapshot.durationInMs, false) + `${e!.description ? ', ' : ''}` : ''}${e!.description ? e!.description : ''}`} </small> </Button> - <div> - <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} small={true} /> - <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} small={true} /> - </div> + <IconButton svg={ArrowUpward} data-id={e!.snapshot.id} title='Move Up' onClick={this.moveUp} flex='20px' /> + <IconButton svg={ArrowDownward} data-id={e!.snapshot.id} title='Move Down' onClick={this.moveDown} flex='20px' /> + <IconButton svg={SwapHoriz} data-id={e!.snapshot.id} title='Replace' onClick={this.replace} flex='20px' /> + <IconButton svg={DeleteOutlined} data-id={e!.snapshot.id} title='Remove' onClick={this.remove} flex='20px' /> </li>)} </ul>; } @@ -235,20 +239,14 @@ export class RemoteStateSnapshots extends PluginUIComponent< this.setState({ isBusy: true }); 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, { name: this.state.params.name, description: this.state.params.options.description, 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.plugin.log.message('Snapshot uploaded.'); this.refresh(); diff --git a/src/mol-plugin-ui/viewport/screenshot.tsx b/src/mol-plugin-ui/viewport/screenshot.tsx index de54188fc..5ff9f4a3e 100644 --- a/src/mol-plugin-ui/viewport/screenshot.tsx +++ b/src/mol-plugin-ui/viewport/screenshot.tsx @@ -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> </div> <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 /> - <div className='msp-help-description' style={{ padding: '10px'}}> - <Icon svg={Warning} /> This is an experimental feature and states stored today might not be openable in an upcoming version. + <div className='msp-help-text' style={{ padding: '10px'}}> + <Icon svg={Warning} /> This is an experimental feature and stored states might not be openable in a future version. </div> </ExpandGroup> </div>; diff --git a/src/mol-plugin/behavior/static/state.ts b/src/mol-plugin/behavior/static/state.ts index 092f632f8..5e1abd3dc 100644 --- a/src/mol-plugin/behavior/static/state.ts +++ b/src/mol-plugin/behavior/static/state.ts @@ -163,19 +163,19 @@ export function Snapshots(ctx: PluginContext) { 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 || '')}`), { method: 'POST', mode: 'cors', referrer: 'no-referrer', 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>; }); PluginCommands.State.Snapshots.Fetch.subscribe(ctx, async ({ url }) => { 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 }) => { diff --git a/src/mol-plugin/commands.ts b/src/mol-plugin/commands.ts index 5367b6dc0..364b6055a 100644 --- a/src/mol-plugin/commands.ts +++ b/src/mol-plugin/commands.ts @@ -34,7 +34,7 @@ export const PluginCommands = { Apply: PluginCommand<{ id: string }>(), 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 }>(), DownloadToFile: PluginCommand<{ name?: string, type: 'json' | 'zip' }>(), diff --git a/src/mol-plugin/state.ts b/src/mol-plugin/state.ts index 710c7548d..8a6baf1a4 100644 --- a/src/mol-plugin/state.ts +++ b/src/mol-plugin/state.ts @@ -56,7 +56,7 @@ class PluginState { canvas3d: p.canvas3d ? { props: this.plugin.canvas3d?.props } : void 0, interactivity: p.interactivity ? { props: this.plugin.managers.interactivity.props } : void 0, structureFocus: this.plugin.managers.structure.focus.getSnapshot(), - durationInMs: params && params.durationInMs + durationInMs: p?.durationInMs }; } -- GitLab