import * as I from 'immutable'; import immutablediff from 'immutablediff'; export class StateSynchronizer { constructor(statefuls) { this._previousState = I.Map(); this._state = I.Map(); this._statefuls = statefuls; this.updateState(); } acceptStateChange(change) { for (const key in change) { const stateful = this._statefuls[key]; if (!stateful) { continue; } stateful.acceptStateChange(change[key]); } } diff() { const state = this.state(); // if (this._previousState === state) { // return StateSynchronizer.noChange; // } const diff = {}; let dirty = false; for (const key in this._statefuls) { if (this._previousState.get(key) !== state.get(key)) { diff[key] = this.diffStateful( this._previousState.get(key), state.get(key) ); dirty = true; } } // Side-effect. this._previousState = this.state(); if (!dirty) { return StateSynchronizer.noChange; } return diff; } diffStateful(previous, current) { // Take a pure JS diff. const steps = immutablediff(previous, current).toJS(); let diff = {}; for (const {op, path, value} of steps) { if ('replace' === op || 'add' === op) { if ('/' === path) { diff = value; } else { const parts = path.split('/'); parts.shift(); let walk = diff; for (let i = 0; i < parts.length; ++i) { const part = parts[i]; walk[part] = walk[part] || {}; if (i === parts.length - 1) { walk[part] = value; } else { walk = walk[part]; } } } } } return diff; } state() { this.updateState(); return this._state; } updateState() { for (const key in this._statefuls) { const stateful = this._statefuls[key]; this._state = this._state.set(key, stateful.state()); } } } StateSynchronizer.noChange = {};