awv3
Version:
⚡ AWV3 embedded CAD
144 lines (131 loc) • 5.36 kB
JavaScript
import v4 from 'uuid-v4';
import omit from 'lodash/omit';
import cloneDeep from 'lodash/cloneDeep';
import { halt } from '../core/error';
import { createObserver } from './helpers';
// Store mixin which creates four actions: register(id, props), unregister(id), update(id, props) & patch(props)
export const mixin = (name, subReducer) => {
const references = {};
const types = {
register: `${name}/REGISTER`,
registerQueue: `${name}/REGISTERQUEUE`,
unregister: `${name}/UNREGISTER`,
update: `${name}/UPDATE`,
merge: `${name}/MERGE`
};
return {
references,
types,
actions: {
register: (reference, props) => dispatch => {
// Store references to the actual lifecycle class making the register call
references[reference.id] = reference;
dispatch({ type: types.register, id: reference.id, props });
},
unregister: ids => dispatch => {
let objects = Array.isArray(ids) ? ids : [ids];
dispatch({ type: types.unregister, ids });
objects.forEach(id => {
// Destroy & remove reference
if (references[id]) {
references[id].destroy(false);
delete references[id];
}
});
},
update: (id, props) => ({ type: types.update, id, props }),
merge: props => ({ type: types.merge, props })
},
reducer(state = {}, { type, id, ...payload }) {
switch (type) {
case types.register:
return { ...state, [id]: { id: id, ...payload.props } };
case types.unregister:
return omit(state, payload.ids);
case types.update:
return { ...state, [id]: { ...state[id], ...payload.props } };
case types.merge:
return Object.keys(payload.props)
.reduce(
(prev, key) => ({ ...prev, [key]: { id: key, ...prev[key], ...payload.props[key] } }),
state
);
default:
if (payload && id && state[id]) {
return { ...state, [id]: subReducer(state[id], { type, ...payload }) };
}
}
return state;
}
};
};
// Base for all objects tied to the store. Lifecycle takes care of the initial
// register, watches for removal, manages child-relations and observers
// Lifecycle eligiable objects must have store actions that expose:
// 1. register(id, props) method
// 2. unregister(id) method
// 3. update(id, props) method
export default (Base = class {}, arg) => class Lifecycle extends Base {
constructor(
session = halt('Lifecycle object is missing a session'),
actions = halt('Lifecycle object is missing link to actions'),
selector = halt('Lifecycle object is missing a selector'),
props
) {
super(arg);
this.id = v4();
this.session = session;
this.store = session.store;
this.actions = actions;
this.getState = () => selector(this.store.getState());
this.subscriptions = new Set();
this.dependencies = [];
// Register object and store original properties
this.store.dispatch(actions.register(this, props));
this.props = cloneDeep(props);
// Create managed observer
let observer = createObserver(this.store, this.getState);
this.observe = (selector, callback, options) =>
observer(selector, callback, { manager: this.managed && this.addSubscription.bind(this), ...options });
// Make props available on the object with automatic setters/getters
for (let prop in props) {
!this[prop] &&
Object.defineProperty(this, prop, {
configurable: true,
get: () => {
let state = this.getState();
return state && state[prop];
},
set: value => this.store.dispatch(actions.update(this.id, { [prop]: value }))
});
}
}
reset() {
this.store.dispatch(this.actions.update(this.id, this.props));
}
destroy(dispatchUnregister = true) {
if (dispatchUnregister) {
this.store.dispatch(this.actions.unregister(this.id));
} else {
this.removeSubscriptions();
this.__onDestroyed && this.__onDestroyed();
this.onDestroyed();
this.dependencies.slice().forEach(child => child.destroy());
this.dependencies = [];
}
}
addSubscription(unsubscribe) {
this.subscriptions.add(unsubscribe);
return unsubscribe;
}
removeSubscription(unsubscribe) {
unsubscribe();
this.subscriptions.delete(unsubscribe);
}
removeSubscriptions() {
this.subscriptions.forEach(unsubscribe => unsubscribe());
this.subscriptions.clear();
this.dependencies.forEach(child => child.removeSubscriptions());
}
onDestroyed() {}
};