From 907672dcc379f59a75b5dc2953e5ea72b802a88c Mon Sep 17 00:00:00 2001 From: David Sehnal <david.sehnal@gmail.com> Date: Mon, 4 Mar 2019 13:35:14 +0100 Subject: [PATCH] wip reworking snapshots --- src/mol-plugin/state.ts | 61 +++++++++++++----- src/mol-plugin/state/animation/manager.ts | 7 ++- src/mol-plugin/state/snapshots.ts | 63 ++++++++++++++++--- .../state/transforms/representation.ts | 2 +- src/mol-plugin/ui/state.tsx | 6 +- 5 files changed, 109 insertions(+), 30 deletions(-) diff --git a/src/mol-plugin/state.ts b/src/mol-plugin/state.ts index 9c5e4c13b..20319010c 100644 --- a/src/mol-plugin/state.ts +++ b/src/mol-plugin/state.ts @@ -14,6 +14,8 @@ import { RxEventHelper } from 'mol-util/rx-event-helper'; import { Canvas3DProps } from 'mol-canvas3d/canvas3d'; import { PluginCommands } from './command'; import { PluginAnimationManager } from './state/animation/manager'; +import { ParamDefinition as PD } from 'mol-util/param-definition'; +import { UUID } from 'mol-util'; export { PluginState } class PluginState { @@ -40,31 +42,42 @@ class PluginState { } } - getSnapshot(): PluginState.Snapshot { + getSnapshot(params?: Partial<PluginState.GetSnapshotParams>): PluginState.Snapshot { + const p = { ...PluginState.DefaultGetSnapshotParams, ...params }; return { - data: this.dataState.getSnapshot(), - behaviour: this.behaviorState.getSnapshot(), - animation: this.animation.getSnapshot(), - cameraSnapshots: this.cameraSnapshots.getStateSnapshot(), - canvas3d: { - camera: this.plugin.canvas3d.camera.getSnapshot(), - viewport: this.plugin.canvas3d.props - } + id: UUID.create22(), + data: p.data ? this.dataState.getSnapshot() : void 0, + behaviour: p.behavior ? this.behaviorState.getSnapshot() : void 0, + animation: p.animation ? this.animation.getSnapshot() : void 0, + camera: p.camera ? { + current: this.plugin.canvas3d.camera.getSnapshot(), + transitionStyle: p.cameraTranstionStyle ? p.cameraTranstionStyle : 'instant' + } : void 0, + cameraSnapshots: p.cameraSnapshots ? this.cameraSnapshots.getStateSnapshot() : void 0, + canvas3d: p.canvas3d ? { + props: this.plugin.canvas3d.props + } : void 0 }; } async setSnapshot(snapshot: PluginState.Snapshot) { + this.animation.stop(); + if (snapshot.behaviour) await this.plugin.runTask(this.behaviorState.setSnapshot(snapshot.behaviour)); if (snapshot.data) await this.plugin.runTask(this.dataState.setSnapshot(snapshot.data)); - if (snapshot.cameraSnapshots) this.cameraSnapshots.setStateSnapshot(snapshot.cameraSnapshots); if (snapshot.canvas3d) { - if (snapshot.canvas3d.viewport) PluginCommands.Canvas3D.SetSettings.dispatch(this.plugin, { settings: snapshot.canvas3d.viewport || { } }); - if (snapshot.canvas3d.camera) this.plugin.canvas3d.camera.setState(snapshot.canvas3d.camera); + if (snapshot.canvas3d.props) await PluginCommands.Canvas3D.SetSettings.dispatch(this.plugin, { settings: snapshot.canvas3d.props || { } }); } - this.plugin.canvas3d.requestDraw(true); + if (snapshot.cameraSnapshots) this.cameraSnapshots.setStateSnapshot(snapshot.cameraSnapshots); if (snapshot.animation) { this.animation.setSnapshot(snapshot.animation); } + if (snapshot.camera) { + await PluginCommands.Camera.SetSnapshot.dispatch(this.plugin, { + snapshot: snapshot.camera.current, + durationMs: snapshot.camera.transitionStyle === 'animate' ? 250 : void 0 + }); + } } dispose() { @@ -95,14 +108,32 @@ class PluginState { namespace PluginState { export type Kind = 'data' | 'behavior' + export type CameraTransitionStyle = 'instant' | 'animate' + export const GetSnapshotParams = { + data: PD.Boolean(true), + behavior: PD.Boolean(false), + animation: PD.Boolean(true), + canvas3d: PD.Boolean(true), + camera: PD.Boolean(true), + // TODO: make camera snapshots same as the StateSnapshots with "child states?" + cameraSnapshots: PD.Boolean(false), + cameraTranstionStyle: PD.Select<CameraTransitionStyle>('animate', [['animate', 'Animate'], ['instant', 'Instant']]) + }; + export type GetSnapshotParams = PD.Value<typeof GetSnapshotParams> + export const DefaultGetSnapshotParams = PD.getDefaultValues(GetSnapshotParams); + export interface Snapshot { + id: UUID, data?: State.Snapshot, behaviour?: State.Snapshot, animation?: PluginAnimationManager.Snapshot, + camera?: { + current: Camera.Snapshot, + transitionStyle: CameraTransitionStyle + }, cameraSnapshots?: CameraSnapshotManager.StateSnapshot, canvas3d?: { - camera?: Camera.Snapshot, - viewport?: Canvas3DProps + props?: Canvas3DProps } } } diff --git a/src/mol-plugin/state/animation/manager.ts b/src/mol-plugin/state/animation/manager.ts index 89a2c792a..5e3cb0987 100644 --- a/src/mol-plugin/state/animation/manager.ts +++ b/src/mol-plugin/state/animation/manager.ts @@ -103,8 +103,11 @@ class PluginAnimationManager extends PluginComponent<PluginAnimationManager.Stat stop() { this.context.canvas3d.setSceneAnimating(false); if (typeof this._frame !== 'undefined') cancelAnimationFrame(this._frame); - this.updateState({ animationState: 'stopped' }); - this.triggerUpdate(); + + if (this.state.animationState !== 'stopped') { + this.updateState({ animationState: 'stopped' }); + this.triggerUpdate(); + } } get isAnimating() { diff --git a/src/mol-plugin/state/snapshots.ts b/src/mol-plugin/state/snapshots.ts index d9f4152d2..3aed583fc 100644 --- a/src/mol-plugin/state/snapshots.ts +++ b/src/mol-plugin/state/snapshots.ts @@ -11,7 +11,7 @@ import { PluginComponent } from 'mol-plugin/component'; export { PluginStateSnapshotManager } -class PluginStateSnapshotManager extends PluginComponent<{ entries: OrderedMap<string, PluginStateSnapshotManager.Entry> }> { +class PluginStateSnapshotManager extends PluginComponent<{ current?: UUID | undefined, entries: OrderedMap<string, PluginStateSnapshotManager.Entry> }> { readonly events = { changed: this.ev() }; @@ -22,40 +22,85 @@ class PluginStateSnapshotManager extends PluginComponent<{ entries: OrderedMap<s remove(id: string) { if (!this.state.entries.has(id)) return; - this.updateState({ entries: this.state.entries.delete(id) }); + this.updateState({ + current: this.state.current === id ? void 0 : this.state.current, + entries: this.state.entries.delete(id) + }); this.events.changed.next(); } add(e: PluginStateSnapshotManager.Entry) { - this.updateState({ entries: this.state.entries.set(e.id, e) }); + this.updateState({ current: e.snapshot.id, entries: this.state.entries.set(e.snapshot.id, e) }); this.events.changed.next(); } clear() { if (this.state.entries.size === 0) return; - this.updateState({ entries: OrderedMap<string, PluginStateSnapshotManager.Entry>() }); + this.updateState({ current: void 0, entries: OrderedMap<string, PluginStateSnapshotManager.Entry>() }); this.events.changed.next(); } + setCurrent(id: string) { + const e = this.getEntry(id); + if (e) { + this.updateState({ current: id as UUID }); + this.events.changed.next(); + } + return e && e.snapshot; + } + + setRemoteSnapshot(snapshot: PluginStateSnapshotManager.RemoteSnapshot): PluginState.Snapshot | undefined { + this.clear(); + const entries = this.state.entries.withMutations(m => { + for (const e of snapshot.entries) { + m.set(e.snapshot.id, e); + } + }); + const current = snapshot.current + ? snapshot.current + : snapshot.entries.length > 0 + ? snapshot.entries[0].snapshot.id + : void 0; + this.updateState({ current, entries }); + this.events.changed.next(); + if (!current) return; + const ret = this.getEntry(current); + return ret && ret.snapshot; + } + + getRemoteSnapshot(options?: { name?: string, description?: string }): PluginStateSnapshotManager.RemoteSnapshot { + // TODO: diffing and all that fancy stuff + return { + timestamp: +new Date(), + name: options && options.name, + description: options && options.description, + current: this.state.current, + entries: this.state.entries.valueSeq().toArray() + }; + } + constructor() { - super({ entries: OrderedMap<string, PluginStateSnapshotManager.Entry>() }); + super({ current: void 0, entries: OrderedMap<string, PluginStateSnapshotManager.Entry>() }); } } namespace PluginStateSnapshotManager { export interface Entry { - id: UUID, - timestamp: string, + timestamp: number, name?: string, description?: string, snapshot: PluginState.Snapshot } export function Entry(snapshot: PluginState.Snapshot, name?: string, description?: string): Entry { - return { id: UUID.create22(), timestamp: new Date().toLocaleString(), name, snapshot, description }; + return { timestamp: +new Date(), name, snapshot, description }; } - export interface StateSnapshot { + export interface RemoteSnapshot { + timestamp: number, + name?: string, + description?: string, + current: UUID | undefined, entries: Entry[] } } \ No newline at end of file diff --git a/src/mol-plugin/state/transforms/representation.ts b/src/mol-plugin/state/transforms/representation.ts index cf85034ea..08b0c3275 100644 --- a/src/mol-plugin/state/transforms/representation.ts +++ b/src/mol-plugin/state/transforms/representation.ts @@ -161,7 +161,7 @@ const StructureRepresentation3D = PluginStateTransform.BuiltIn({ })({ canAutoUpdate({ a, oldParams, newParams }) { // TODO: other criteria as well? - return a.data.elementCount < 10000 && oldParams.type.name === newParams.type.name; + return a.data.elementCount < 10000 || oldParams.type.name === newParams.type.name; }, apply({ a, params }, plugin: PluginContext) { return Task.create('Structure Representation', async ctx => { diff --git a/src/mol-plugin/ui/state.tsx b/src/mol-plugin/ui/state.tsx index 6e13eb42e..aaadc4b77 100644 --- a/src/mol-plugin/ui/state.tsx +++ b/src/mol-plugin/ui/state.tsx @@ -110,9 +110,9 @@ class LocalStateSnapshotList extends PluginUIComponent<{ }, { }> { render() { return <ul style={{ listStyle: 'none' }} className='msp-state-list'> - {this.plugin.state.snapshots.state.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'> + {this.plugin.state.snapshots.state.entries.valueSeq().map(e =><li key={e!.snapshot.id}> + <button className='msp-btn msp-btn-block msp-form-control' onClick={this.apply(e!.snapshot.id)}>{e!.name || new Date(e!.timestamp).toLocaleString()} <small>{e!.description}</small></button> + <button onClick={this.remove(e!.snapshot.id)} className='msp-btn msp-btn-link msp-state-list-remove-button'> <span className='msp-icon msp-icon-remove' /> </button> </li>)} -- GitLab