diff --git a/src/mol-plugin/behavior/static/state.ts b/src/mol-plugin/behavior/static/state.ts index af9541687760557e436bb8c23116f7b78b2bbfc1..420ae299c5cd9b6dec8b003d07c995071436ae86 100644 --- a/src/mol-plugin/behavior/static/state.ts +++ b/src/mol-plugin/behavior/static/state.ts @@ -11,6 +11,8 @@ import { PluginStateSnapshotManager } from 'mol-plugin/state/snapshots'; import { PluginStateObject as SO, PluginStateObject } from '../../state/objects'; import { EmptyLoci, EveryLoci } from 'mol-model/loci'; import { Structure } from 'mol-model/structure'; +import { getFormattedTime } from 'mol-util/date'; +import { readFromFile } from 'mol-util/data-source'; export function registerDefault(ctx: PluginContext) { SyncBehaviors(ctx); @@ -134,4 +136,33 @@ export function Snapshots(ctx: PluginContext) { const json = await req.json(); return ctx.state.setSnapshot(json.data); }); + + PluginCommands.State.Snapshots.DownloadToFile.subscribe(ctx, ({ name }) => { + const element = document.createElement('a'); + const json = JSON.stringify(ctx.state.getSnapshot(), null, 2); + element.setAttribute('href', 'data:application/json;charset=utf-8,' + encodeURIComponent(json)); + element.setAttribute('download', `mol-star_state_${(name || getFormattedTime())}.json`); + element.style.display = 'none'; + document.body.appendChild(element); + element.click(); + document.body.removeChild(element); + }); + + PluginCommands.State.Snapshots.OpenFile.subscribe(ctx, async ({ file }) => { + try { + const data = await readFromFile(file, 'string').run(); + const json = JSON.parse(data as string); + await ctx.state.setSnapshot(json); + } catch (e) { + ctx.log.error(`Reading JSON state: ${e}`); + } + // const element = document.createElement('a'); + // const json = JSON.stringify(ctx.state.getSnapshot(), null, 2); + // element.setAttribute('href', 'data:application/json;charset=utf-8,' + encodeURIComponent(json)); + // element.setAttribute('download', `mol-star_state_${(name || getFormattedTime())}.json`); + // element.style.display = 'none'; + // document.body.appendChild(element); + // element.click(); + // document.body.removeChild(element); + }); } \ No newline at end of file diff --git a/src/mol-plugin/command.ts b/src/mol-plugin/command.ts index 211e70870c8e93e48729cb72aa6d03caf10a4512..6e8cfff331fc7eab6c2f103796eeb1048614ae30 100644 --- a/src/mol-plugin/command.ts +++ b/src/mol-plugin/command.ts @@ -32,7 +32,10 @@ export const PluginCommands = { Clear: PluginCommand<{}>({ isImmediate: true }), Upload: PluginCommand<{ name?: string, description?: string, serverUrl: string }>({ isImmediate: true }), - Fetch: PluginCommand<{ url: string }>() + Fetch: PluginCommand<{ url: string }>(), + + DownloadToFile: PluginCommand<{ name?: string }>({ isImmediate: true }), + OpenFile: PluginCommand<{ file: File }>({ isImmediate: true }), } }, Camera: { diff --git a/src/mol-plugin/ui/state.tsx b/src/mol-plugin/ui/state.tsx index a30896fb1fc4c12d4b5cf9736ab68324724cb0ae..b9a16ad129c0e3dd4fc737170b9675fe757ba850 100644 --- a/src/mol-plugin/ui/state.tsx +++ b/src/mol-plugin/ui/state.tsx @@ -61,6 +61,16 @@ class StateSnapshotControls extends PluginComponent<{ serverUrl: string, serverC 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 => { @@ -73,6 +83,12 @@ class StateSnapshotControls extends PluginComponent<{ serverUrl: string, serverC <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>; } } diff --git a/src/mol-util/date.ts b/src/mol-util/date.ts index 90d03997b579862440fc0dc3a30c8663a6ee48cc..cfb4cdb3789979b23b4a26083e93386c97ca2b13 100644 --- a/src/mol-util/date.ts +++ b/src/mol-util/date.ts @@ -6,4 +6,15 @@ export function dateToUtcString(date: Date) { return date.toISOString().replace(/T/, ' ').replace(/\..+/, ''); +} + +export function getFormattedTime() { + const today = new Date(); + const y = today.getFullYear(); + const m = today.getMonth() + 1; + const d = today.getDate(); + const h = today.getHours(); + const mi = today.getMinutes(); + const s = today.getSeconds(); + return y + '-' + m + '-' + d + '-' + h + '-' + mi + '-' + s; } \ No newline at end of file