2019-03-20 11:49:41 +01:00
|
|
|
import { EventBus } from "./event_bus";
|
2019-03-22 09:52:48 +01:00
|
|
|
import { Component } from "./component";
|
2019-03-20 11:49:41 +01:00
|
|
|
|
2019-04-11 13:00:56 +02:00
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
|
// Store Definition
|
|
|
|
|
//------------------------------------------------------------------------------
|
2019-03-20 11:49:41 +01:00
|
|
|
interface StoreConfig {
|
2019-03-29 11:23:44 +01:00
|
|
|
env?: any;
|
2019-03-20 11:49:41 +01:00
|
|
|
state?: any;
|
|
|
|
|
actions?: any;
|
|
|
|
|
mutations?: any;
|
|
|
|
|
}
|
2019-03-22 10:56:34 +01:00
|
|
|
|
|
|
|
|
interface StoreOption {
|
|
|
|
|
debug?: boolean;
|
|
|
|
|
}
|
2019-04-11 13:08:00 +02:00
|
|
|
|
2019-03-20 11:49:41 +01:00
|
|
|
export class Store extends EventBus {
|
2019-04-08 23:02:23 +02:00
|
|
|
state: any;
|
2019-03-20 11:49:41 +01:00
|
|
|
actions: any;
|
|
|
|
|
mutations: any;
|
|
|
|
|
_isMutating: boolean = false;
|
2019-03-22 10:56:34 +01:00
|
|
|
history: any[] = [];
|
|
|
|
|
debug: boolean;
|
2019-03-29 11:23:44 +01:00
|
|
|
env: any;
|
2019-04-11 13:08:00 +02:00
|
|
|
observer: Observer;
|
2019-03-20 11:49:41 +01:00
|
|
|
|
2019-03-22 10:56:34 +01:00
|
|
|
constructor(config: StoreConfig, options: StoreOption = {}) {
|
2019-03-20 11:49:41 +01:00
|
|
|
super();
|
2019-03-22 10:56:34 +01:00
|
|
|
this.debug = options.debug || false;
|
2019-04-11 13:08:00 +02:00
|
|
|
this.state = config.state || {};
|
2019-03-20 11:49:41 +01:00
|
|
|
this.actions = config.actions;
|
|
|
|
|
this.mutations = config.mutations;
|
2019-03-29 11:23:44 +01:00
|
|
|
this.env = config.env;
|
2019-04-11 13:08:00 +02:00
|
|
|
this.observer = makeObserver();
|
2019-04-12 12:05:06 +02:00
|
|
|
this.observer.allowMutations = false;
|
2019-04-11 13:08:00 +02:00
|
|
|
this.observer.observe(this.state);
|
2019-03-22 10:56:34 +01:00
|
|
|
|
|
|
|
|
if (this.debug) {
|
|
|
|
|
this.history.push({ state: this.state });
|
|
|
|
|
}
|
2019-03-20 11:49:41 +01:00
|
|
|
}
|
|
|
|
|
|
2019-04-09 13:16:22 +02:00
|
|
|
dispatch(action, payload?: any): Promise<void> | void {
|
2019-03-20 11:49:41 +01:00
|
|
|
if (!this.actions[action]) {
|
|
|
|
|
throw new Error(`[Error] action ${action} is undefined`);
|
|
|
|
|
}
|
2019-04-09 13:16:22 +02:00
|
|
|
const result = this.actions[action](
|
2019-03-20 11:49:41 +01:00
|
|
|
{
|
|
|
|
|
commit: this.commit.bind(this),
|
2019-04-08 22:58:45 +02:00
|
|
|
dispatch: this.dispatch.bind(this),
|
|
|
|
|
env: this.env,
|
|
|
|
|
state: this.state
|
2019-03-20 11:49:41 +01:00
|
|
|
},
|
|
|
|
|
payload
|
|
|
|
|
);
|
2019-04-09 13:16:22 +02:00
|
|
|
if (result instanceof Promise) {
|
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
|
result.then(() => resolve());
|
|
|
|
|
result.catch(reject);
|
|
|
|
|
});
|
|
|
|
|
}
|
2019-03-20 11:49:41 +01:00
|
|
|
}
|
|
|
|
|
|
2019-04-08 23:02:23 +02:00
|
|
|
async commit(type, payload?: any) {
|
2019-03-20 11:49:41 +01:00
|
|
|
if (!this.mutations[type]) {
|
|
|
|
|
throw new Error(`[Error] mutation ${type} is undefined`);
|
|
|
|
|
}
|
2019-04-11 13:08:00 +02:00
|
|
|
const currentRev = this.observer.__rev__;
|
2019-03-21 09:11:44 +01:00
|
|
|
|
2019-04-12 12:05:06 +02:00
|
|
|
this._isMutating = true;
|
|
|
|
|
this.observer.allowMutations = true;
|
2019-04-11 13:08:00 +02:00
|
|
|
this.mutations[type].call(null, this.state, payload);
|
2019-04-12 12:05:06 +02:00
|
|
|
this.observer.allowMutations = false;
|
|
|
|
|
|
2019-03-22 10:56:34 +01:00
|
|
|
if (this.debug) {
|
|
|
|
|
this.history.push({
|
|
|
|
|
state: this.state,
|
|
|
|
|
mutation: type,
|
|
|
|
|
payload: payload
|
|
|
|
|
});
|
|
|
|
|
}
|
2019-03-21 09:11:44 +01:00
|
|
|
await Promise.resolve();
|
|
|
|
|
if (this._isMutating) {
|
|
|
|
|
this._isMutating = false;
|
2019-04-11 13:08:00 +02:00
|
|
|
// this.observer.disableMutations();
|
|
|
|
|
if (currentRev !== this.observer.__rev__) {
|
2019-04-08 23:02:23 +02:00
|
|
|
this.trigger("update", this.state);
|
|
|
|
|
}
|
2019-03-21 09:11:44 +01:00
|
|
|
}
|
2019-03-20 11:49:41 +01:00
|
|
|
}
|
2019-04-08 23:02:23 +02:00
|
|
|
}
|
2019-03-20 11:49:41 +01:00
|
|
|
|
2019-04-11 13:08:00 +02:00
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
|
// Observer
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
|
interface Observer {
|
|
|
|
|
__rev__: number;
|
2019-04-12 12:05:06 +02:00
|
|
|
allowMutations: boolean;
|
2019-04-11 13:08:00 +02:00
|
|
|
observe: (val: any) => void;
|
|
|
|
|
set: (target: any, key: number | string, value: any) => void;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function makeObserver(): Observer {
|
|
|
|
|
const observer: Observer = {
|
|
|
|
|
__rev__: 0,
|
2019-04-12 12:05:06 +02:00
|
|
|
allowMutations: true,
|
2019-04-11 13:08:00 +02:00
|
|
|
observe: observe,
|
|
|
|
|
set: set
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
function set(target: any, key: number | string, value: any) {
|
|
|
|
|
if (Array.isArray(target)) {
|
|
|
|
|
} else {
|
|
|
|
|
addProp(target, key as string, value);
|
|
|
|
|
}
|
|
|
|
|
target.__rev__++;
|
|
|
|
|
observer.__rev__++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function addProp<T extends { __rev__?: number }>(
|
|
|
|
|
obj: T,
|
|
|
|
|
key: string,
|
|
|
|
|
value: any
|
|
|
|
|
) {
|
|
|
|
|
Object.defineProperty(obj, key, {
|
|
|
|
|
get() {
|
|
|
|
|
return value;
|
|
|
|
|
},
|
|
|
|
|
set(newVal) {
|
2019-04-12 12:05:06 +02:00
|
|
|
if (!observer.allowMutations) {
|
|
|
|
|
throw new Error("State cannot be changed outside a mutation!");
|
|
|
|
|
}
|
2019-04-11 13:08:00 +02:00
|
|
|
if (newVal !== value) {
|
|
|
|
|
value = newVal;
|
2019-04-12 12:08:31 +02:00
|
|
|
observe(newVal);
|
2019-04-11 13:08:00 +02:00
|
|
|
obj.__rev__!++;
|
|
|
|
|
observer.__rev__++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
observe(value);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function observeObj<T extends { __rev__?: number }>(obj: T) {
|
|
|
|
|
const keys = Object.keys(obj);
|
|
|
|
|
obj.__rev__ = 0;
|
|
|
|
|
Object.defineProperty(obj, "__rev__", { enumerable: false });
|
|
|
|
|
for (let key of keys) {
|
|
|
|
|
addProp(obj, key, obj[key]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const ArrayProto = Array.prototype;
|
|
|
|
|
const ModifiedArrayProto = Object.create(ArrayProto);
|
|
|
|
|
|
|
|
|
|
ModifiedArrayProto.push = function(...args) {
|
|
|
|
|
observer.__rev__++;
|
|
|
|
|
this.__rev__++;
|
|
|
|
|
return ArrayProto.push.call(this, ...args);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
ModifiedArrayProto.pop = function(...args) {
|
|
|
|
|
observer.__rev__++;
|
|
|
|
|
this.__rev__++;
|
|
|
|
|
return ArrayProto.pop.call(this, ...args);
|
|
|
|
|
};
|
|
|
|
|
function observeArr(arr: Array<any>) {
|
|
|
|
|
(<any>arr).__rev__ = 0;
|
|
|
|
|
Object.defineProperty(arr, "__rev__", { enumerable: false });
|
|
|
|
|
(<any>arr).__proto__ = ModifiedArrayProto;
|
|
|
|
|
// for (let i = 0; i < arr.length; i++) {
|
|
|
|
|
// observe(arr[i]);
|
|
|
|
|
// }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function observe(value: any) {
|
|
|
|
|
if (typeof value !== "object") {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2019-04-12 12:17:16 +02:00
|
|
|
if ("__rev__" in value) {
|
|
|
|
|
// already observed
|
|
|
|
|
return;
|
|
|
|
|
}
|
2019-04-11 13:08:00 +02:00
|
|
|
if (Array.isArray(value)) {
|
|
|
|
|
observeArr(value);
|
|
|
|
|
} else {
|
|
|
|
|
observeObj(value);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return observer;
|
|
|
|
|
}
|
|
|
|
|
|
2019-04-11 13:00:56 +02:00
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
|
// Connect function
|
|
|
|
|
//------------------------------------------------------------------------------
|
2019-04-11 13:08:00 +02:00
|
|
|
|
|
|
|
|
function setStoreProps(__widget__: any, storeProps: any) {
|
|
|
|
|
__widget__.currentStoreProps = storeProps;
|
|
|
|
|
__widget__.currentStoreRevs = {};
|
|
|
|
|
__widget__.currentStoreRev = storeProps.__rev__;
|
|
|
|
|
for (let key in storeProps) {
|
|
|
|
|
__widget__.currentStoreRevs[key] = storeProps[key].__rev__;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-04-08 23:02:23 +02:00
|
|
|
export function connect(mapStateToProps) {
|
|
|
|
|
return function(Comp) {
|
|
|
|
|
return class extends Comp {
|
|
|
|
|
constructor(parent, props?: any) {
|
|
|
|
|
const env = parent instanceof Component ? parent.env : parent;
|
|
|
|
|
const ownProps = Object.assign({}, props || {});
|
|
|
|
|
const storeProps = mapStateToProps(env.store.state, ownProps);
|
|
|
|
|
const mergedProps = Object.assign(props || {}, storeProps);
|
|
|
|
|
super(parent, mergedProps);
|
2019-04-11 13:08:00 +02:00
|
|
|
setStoreProps(this.__widget__, storeProps);
|
2019-04-08 23:02:23 +02:00
|
|
|
this.__widget__.ownProps = ownProps;
|
|
|
|
|
}
|
|
|
|
|
mounted() {
|
|
|
|
|
this.env.store.on("update", this, () => {
|
|
|
|
|
const ownProps = this.__widget__.ownProps;
|
|
|
|
|
const storeProps = mapStateToProps(this.env.store.state, ownProps);
|
2019-04-11 13:08:00 +02:00
|
|
|
let didChange = false;
|
|
|
|
|
if (this.__widget__.currentStoreRev !== storeProps.__rev__) {
|
|
|
|
|
setStoreProps(this.__widget__, storeProps);
|
|
|
|
|
didChange = true;
|
|
|
|
|
} else {
|
|
|
|
|
const revs = this.__widget__.currentStoreRevs;
|
|
|
|
|
for (let key in storeProps) {
|
|
|
|
|
const val = storeProps[key];
|
|
|
|
|
if (val.__rev__ !== revs[key]) {
|
|
|
|
|
didChange = true;
|
|
|
|
|
revs[key] = val.__rev__;
|
|
|
|
|
this.__widget__.currentStoreProps[key] = val;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (didChange) {
|
2019-04-08 23:02:23 +02:00
|
|
|
this.updateProps(ownProps, false);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
super.mounted();
|
|
|
|
|
}
|
|
|
|
|
willUnmount() {
|
|
|
|
|
this.env.store.off("update", this);
|
|
|
|
|
super.willUnmount();
|
|
|
|
|
}
|
|
|
|
|
updateProps(nextProps, forceUpdate) {
|
|
|
|
|
if (this.__widget__.ownProps !== nextProps) {
|
|
|
|
|
this.__widget__.currentStoreProps = mapStateToProps(
|
|
|
|
|
this.env.store.state,
|
|
|
|
|
nextProps
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
this.__widget__.ownProps = nextProps;
|
|
|
|
|
const mergedProps = Object.assign(
|
|
|
|
|
{},
|
|
|
|
|
nextProps,
|
|
|
|
|
this.__widget__.currentStoreProps
|
|
|
|
|
);
|
|
|
|
|
return super.updateProps(mergedProps, forceUpdate);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
};
|
2019-03-20 11:49:41 +01:00
|
|
|
}
|