Newer
Older
/**
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
import { List } from 'immutable';
import { UUID } from 'mol-util';
import { PluginState } from '../state';
import { PluginComponent } from 'mol-plugin/component';
import { PluginContext } from 'mol-plugin/context';
class PluginStateSnapshotManager extends PluginComponent<{
current?: UUID | undefined,
entries: List<PluginStateSnapshotManager.Entry>,
isPlaying: boolean,
nextSnapshotDelayInMs: number
static DefaultNextSnapshotDelayInMs = 1500;
private entryMap = new Map<string, PluginStateSnapshotManager.Entry>();
readonly events = {
changed: this.ev()
};
currentGetSnapshotParams: PluginState.GetSnapshotParams = PluginState.DefaultGetSnapshotParams as any;
getIndex(e: PluginStateSnapshotManager.Entry) {
return this.state.entries.indexOf(e);
}
getEntry(id: string | undefined) {
if (!id) return;
this.updateState({
current: this.state.current === id ? void 0 : this.state.current,
entries: this.state.entries.delete(this.getIndex(e))
this.events.changed.next();
}
add(e: PluginStateSnapshotManager.Entry) {
this.updateState({ current: e.snapshot.id, entries: this.state.entries.push(e) });
this.events.changed.next();
}
replace(id: string, snapshot: PluginState.Snapshot) {
const old = this.getEntry(id);
if (!old) return;
const idx = this.getIndex(old);
// The id changes here!
const e = PluginStateSnapshotManager.Entry(snapshot, {
name: old.name,
description: old.description
});
this.updateState({ current: e.snapshot.id, entries: this.state.entries.set(idx, e) });
this.events.changed.next();
}
move(id: string, dir: -1 | 1) {
const len = this.state.entries.size;
if (len < 2) return;
const e = this.getEntry(id);
if (!e) return;
const from = this.getIndex(e);
let to = (from + dir) % len;
if (to < 0) to += len;
const f = this.state.entries.get(to);
const entries = this.state.entries.asMutable();
entries.set(to, e);
entries.set(from, f);
this.updateState({ current: e.snapshot.id, entries: entries.asImmutable() });
this.events.changed.next();
}
clear() {
if (this.state.entries.size === 0) return;
this.updateState({ current: void 0, entries: List<PluginStateSnapshotManager.Entry>() });
setCurrent(id: string) {
const e = this.getEntry(id);
if (e) {
this.updateState({ current: id as UUID });
this.events.changed.next();
}
return e && e.snapshot;
}
getNextId(id: string | undefined, dir: -1 | 1) {
const len = this.state.entries.size;
if (!id) {
if (len === 0) return void 0;
const idx = dir === -1 ? len - 1 : 0;
return this.state.entries.get(idx).snapshot.id;
const e = this.getEntry(id);
if (!e) return;
let idx = this.getIndex(e);
if (idx < 0) return;
idx = (idx + dir) % len;
if (idx < 0) idx += len;
return this.state.entries.get(idx).snapshot.id;
async setRemoteSnapshot(snapshot: PluginStateSnapshotManager.RemoteSnapshot): Promise<PluginState.Snapshot | undefined> {
const entries = List<PluginStateSnapshotManager.Entry>().asMutable()
for (const e of snapshot.entries) {
const current = snapshot.current
? snapshot.current
: snapshot.entries.length > 0
? snapshot.entries[0].snapshot.id
: void 0;
this.updateState({
current,
entries: entries.asImmutable(),
isPlaying: false,
nextSnapshotDelayInMs: snapshot.playback ? snapshot.playback.nextSnapshotDelayInMs : PluginStateSnapshotManager.DefaultNextSnapshotDelayInMs
});
this.events.changed.next();
if (!current) return;
const entry = this.getEntry(current);
const next = entry && entry.snapshot;
if (!next) return;
await this.plugin.state.setSnapshot(next);
if (snapshot.playback && snapshot.playback.isPlaying) this.play();
return next;
}
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,
playback: {
isPlaying: this.state.isPlaying,
nextSnapshotDelayInMs: this.state.nextSnapshotDelayInMs
},
entries: this.state.entries.valueSeq().toArray()
};
}
private timeoutHandle: any = void 0;
private next = async () => {
this.timeoutHandle = void 0;
const next = this.getNextId(this.state.current, 1);
if (!next || next === this.state.current) {
this.stop();
return;
}
const snapshot = this.setCurrent(next)!;
await this.plugin.state.setSnapshot(snapshot);
const delay = typeof snapshot.durationInMs !== 'undefined' ? snapshot.durationInMs : this.state.nextSnapshotDelayInMs;
David Sehnal
committed
if (this.state.isPlaying) this.timeoutHandle = setTimeout(this.next, delay);
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
};
play() {
this.updateState({ isPlaying: true });
this.next();
}
stop() {
this.updateState({ isPlaying: false });
if (typeof this.timeoutHandle !== 'undefined') clearTimeout(this.timeoutHandle);
this.timeoutHandle = void 0;
this.events.changed.next();
}
togglePlay() {
if (this.state.isPlaying) this.stop();
else this.play();
}
constructor(private plugin: PluginContext) {
super({
current: void 0,
entries: List(),
isPlaying: false,
nextSnapshotDelayInMs: PluginStateSnapshotManager.DefaultNextSnapshotDelayInMs
});
// TODO make nextSnapshotDelayInMs editable
}
}
namespace PluginStateSnapshotManager {
export interface Entry {
description?: string,
snapshot: PluginState.Snapshot
}
export function Entry(snapshot: PluginState.Snapshot, params: {name?: string, description?: string }): Entry {
return { timestamp: +new Date(), snapshot, ...params };
export interface RemoteSnapshot {
timestamp: number,
name?: string,
description?: string,
current: UUID | undefined,
playback: {
isPlaying: boolean,
nextSnapshotDelayInMs: number,
},