2019-03-20 11:49:41 +01:00
|
|
|
import { EventBus } from "./event_bus";
|
2019-04-14 21:37:05 +02:00
|
|
|
import { shallowEqual } from "./utils";
|
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-04-12 12:42:14 +02:00
|
|
|
|
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;
|
2019-04-12 12:42:14 +02:00
|
|
|
mutations?: { [name: string]: any };
|
2019-03-20 11:49:41 +01:00
|
|
|
}
|
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-14 11:27:02 +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-12 12:42:14 +02:00
|
|
|
this.mutations[type].call(
|
|
|
|
|
null,
|
|
|
|
|
{ state: this.state, set: this.observer.set },
|
|
|
|
|
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-14 11:27:02 +02:00
|
|
|
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 {
|
2019-04-14 11:27:02 +02:00
|
|
|
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 = {
|
2019-04-14 11:27:02 +02:00
|
|
|
rev: 1,
|
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) {
|
2019-04-12 12:45:44 +02:00
|
|
|
addProp(target, key, value);
|
2019-04-14 11:27:02 +02:00
|
|
|
target.__owl__.rev++;
|
|
|
|
|
observer.rev++;
|
2019-04-11 13:08:00 +02:00
|
|
|
}
|
|
|
|
|
|
2019-04-14 11:27:02 +02:00
|
|
|
function addProp<T extends { __owl__?: any }>(
|
2019-04-11 13:08:00 +02:00
|
|
|
obj: T,
|
2019-04-12 12:45:44 +02:00
|
|
|
key: string | number,
|
2019-04-11 13:08:00 +02:00
|
|
|
value: any
|
|
|
|
|
) {
|
|
|
|
|
Object.defineProperty(obj, key, {
|
2019-04-12 12:42:14 +02:00
|
|
|
enumerable: true,
|
2019-04-11 13:08:00 +02:00
|
|
|
get() {
|
|
|
|
|
return value;
|
|
|
|
|
},
|
|
|
|
|
set(newVal) {
|
2019-04-12 12:05:06 +02:00
|
|
|
if (!observer.allowMutations) {
|
2019-04-12 12:25:22 +02:00
|
|
|
throw new Error(
|
|
|
|
|
`State cannot be changed outside a mutation! (key: "${key}", val: "${newVal}")`
|
|
|
|
|
);
|
2019-04-12 12:05:06 +02:00
|
|
|
}
|
2019-04-11 13:08:00 +02:00
|
|
|
if (newVal !== value) {
|
2019-04-15 10:43:50 +02:00
|
|
|
unobserve(value);
|
2019-04-11 13:08:00 +02:00
|
|
|
value = newVal;
|
2019-04-14 15:37:27 +02:00
|
|
|
observe(newVal, obj);
|
2019-04-14 11:27:02 +02:00
|
|
|
obj.__owl__.rev!++;
|
|
|
|
|
observer.rev++;
|
2019-04-14 15:37:27 +02:00
|
|
|
let parent = obj;
|
|
|
|
|
do {
|
|
|
|
|
parent.__owl__.deepRev++;
|
|
|
|
|
} while ((parent = parent.__owl__.parent));
|
2019-04-11 13:08:00 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
2019-04-14 15:37:27 +02:00
|
|
|
observe(value, obj);
|
2019-04-11 13:08:00 +02:00
|
|
|
}
|
|
|
|
|
|
2019-04-14 15:37:27 +02:00
|
|
|
function observeObj<T extends { __owl__?: any }>(obj: T, parent?: any) {
|
2019-04-11 13:08:00 +02:00
|
|
|
const keys = Object.keys(obj);
|
2019-04-14 15:37:27 +02:00
|
|
|
obj.__owl__ = { rev: 1, deepRev: 1, parent };
|
2019-04-14 11:27:02 +02:00
|
|
|
Object.defineProperty(obj, "__owl__", { enumerable: false });
|
2019-04-11 13:08:00 +02:00
|
|
|
for (let key of keys) {
|
|
|
|
|
addProp(obj, key, obj[key]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const ArrayProto = Array.prototype;
|
|
|
|
|
const ModifiedArrayProto = Object.create(ArrayProto);
|
|
|
|
|
|
2019-04-12 13:03:25 +02:00
|
|
|
const methodsToPatch = [
|
|
|
|
|
"push",
|
|
|
|
|
"pop",
|
|
|
|
|
"shift",
|
|
|
|
|
"unshift",
|
|
|
|
|
"splice",
|
|
|
|
|
"sort",
|
|
|
|
|
"reverse"
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
for (let method of methodsToPatch) {
|
|
|
|
|
const initialMethod = ArrayProto[method];
|
|
|
|
|
ModifiedArrayProto[method] = function(...args) {
|
2019-04-14 11:27:02 +02:00
|
|
|
observer.rev++;
|
|
|
|
|
this.__owl__.rev++;
|
2019-04-14 15:37:27 +02:00
|
|
|
let parent = this;
|
|
|
|
|
do {
|
|
|
|
|
parent.__owl__.deepRev++;
|
|
|
|
|
} while ((parent = parent.__owl__.parent));
|
2019-04-12 13:03:25 +02:00
|
|
|
let inserted;
|
|
|
|
|
switch (method) {
|
|
|
|
|
case "push":
|
|
|
|
|
case "unshift":
|
|
|
|
|
inserted = args;
|
|
|
|
|
break;
|
|
|
|
|
case "splice":
|
|
|
|
|
inserted = args.slice(2);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
if (inserted) {
|
|
|
|
|
for (let elem of inserted) {
|
2019-04-14 15:37:27 +02:00
|
|
|
observe(elem, this);
|
2019-04-12 13:03:25 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return initialMethod.call(this, ...args);
|
|
|
|
|
};
|
|
|
|
|
}
|
2019-04-11 13:08:00 +02:00
|
|
|
|
2019-04-14 15:37:27 +02:00
|
|
|
function observeArr(arr: Array<any>, parent?: any) {
|
|
|
|
|
(<any>arr).__owl__ = { rev: 1, deepRev: 1, parent };
|
2019-04-14 11:27:02 +02:00
|
|
|
Object.defineProperty(arr, "__owl__", { enumerable: false });
|
2019-04-11 13:08:00 +02:00
|
|
|
(<any>arr).__proto__ = ModifiedArrayProto;
|
2019-04-12 12:25:22 +02:00
|
|
|
for (let i = 0; i < arr.length; i++) {
|
2019-04-14 15:37:27 +02:00
|
|
|
observe(arr[i], arr);
|
2019-04-12 12:25:22 +02:00
|
|
|
}
|
2019-04-11 13:08:00 +02:00
|
|
|
}
|
|
|
|
|
|
2019-04-14 15:37:27 +02:00
|
|
|
function observe(value: any, parent?: any) {
|
2019-04-12 21:07:12 +02:00
|
|
|
if (value === null) {
|
|
|
|
|
// fun fact: typeof null === 'object'
|
|
|
|
|
return;
|
|
|
|
|
}
|
2019-04-11 13:08:00 +02:00
|
|
|
if (typeof value !== "object") {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2019-04-14 11:27:02 +02:00
|
|
|
if ("__owl__" in value) {
|
2019-04-12 12:17:16 +02:00
|
|
|
// already observed
|
|
|
|
|
return;
|
|
|
|
|
}
|
2019-04-11 13:08:00 +02:00
|
|
|
if (Array.isArray(value)) {
|
2019-04-14 15:37:27 +02:00
|
|
|
observeArr(value, parent);
|
2019-04-11 13:08:00 +02:00
|
|
|
} else {
|
2019-04-14 15:37:27 +02:00
|
|
|
observeObj(value, parent);
|
2019-04-11 13:08:00 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-04-15 10:43:50 +02:00
|
|
|
function unobserve(target: any) {
|
|
|
|
|
if (target !== null && typeof target === "object") {
|
|
|
|
|
delete target.__owl__;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-04-11 13:08:00 +02:00
|
|
|
return observer;
|
|
|
|
|
}
|
|
|
|
|
|
2019-04-11 13:00:56 +02:00
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
|
// Connect function
|
|
|
|
|
//------------------------------------------------------------------------------
|
2019-04-11 13:08:00 +02:00
|
|
|
|
2019-04-14 21:37:05 +02:00
|
|
|
function revNumber<T extends Object>(o: T): number {
|
|
|
|
|
if (!("__owl__" in o)) {
|
|
|
|
|
return 0;
|
2019-04-11 13:08:00 +02:00
|
|
|
}
|
2019-04-14 21:37:05 +02:00
|
|
|
return (<any>o).__owl__.rev;
|
2019-04-11 13:08:00 +02:00
|
|
|
}
|
|
|
|
|
|
2019-04-14 21:37:05 +02:00
|
|
|
function deepRevNumber<T extends Object>(o: T): number {
|
|
|
|
|
if (!("__owl__" in o)) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
return (<any>o).__owl__.deepRev;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function connect(mapStateToProps, options: any = {}) {
|
|
|
|
|
let hashFunction = options.hashFunction || null;
|
|
|
|
|
|
2019-04-08 23:02:23 +02:00
|
|
|
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);
|
2019-04-12 12:25:22 +02:00
|
|
|
const mergedProps = Object.assign({}, props || {}, storeProps);
|
2019-04-08 23:02:23 +02:00
|
|
|
super(parent, mergedProps);
|
2019-04-13 14:14:34 +02:00
|
|
|
this.__owl__.ownProps = ownProps;
|
2019-04-14 21:37:05 +02:00
|
|
|
this.__owl__.currentStoreProps = storeProps;
|
|
|
|
|
if (!hashFunction) {
|
|
|
|
|
if ("__owl__" in storeProps) {
|
|
|
|
|
hashFunction = s => deepRevNumber(s.storeProps);
|
|
|
|
|
} else {
|
|
|
|
|
let areKeyObservable = false;
|
|
|
|
|
for (let key in storeProps) {
|
|
|
|
|
areKeyObservable =
|
|
|
|
|
areKeyObservable || "__owl__" in storeProps[key];
|
|
|
|
|
}
|
|
|
|
|
if (areKeyObservable) {
|
|
|
|
|
hashFunction = function({ storeProps }) {
|
|
|
|
|
return Object.values(storeProps).reduce(
|
|
|
|
|
(sum: number, val: any) => sum + deepRevNumber(val),
|
|
|
|
|
0
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (hashFunction) {
|
|
|
|
|
this.__owl__.storeHash = hashFunction({
|
|
|
|
|
state: env.store.state,
|
|
|
|
|
storeProps: storeProps,
|
|
|
|
|
revNumber,
|
|
|
|
|
deepRevNumber
|
|
|
|
|
});
|
|
|
|
|
}
|
2019-04-08 23:02:23 +02:00
|
|
|
}
|
|
|
|
|
mounted() {
|
|
|
|
|
this.env.store.on("update", this, () => {
|
2019-04-13 14:14:34 +02:00
|
|
|
const ownProps = this.__owl__.ownProps;
|
2019-04-08 23:02:23 +02:00
|
|
|
const storeProps = mapStateToProps(this.env.store.state, ownProps);
|
2019-04-11 13:08:00 +02:00
|
|
|
let didChange = false;
|
2019-04-14 21:37:05 +02:00
|
|
|
if (hashFunction) {
|
|
|
|
|
const storeHash = hashFunction({
|
|
|
|
|
state: this.env.store.state,
|
|
|
|
|
storeProps: storeProps,
|
|
|
|
|
revNumber,
|
|
|
|
|
deepRevNumber
|
|
|
|
|
});
|
|
|
|
|
if (storeHash !== this.__owl__.storeHash) {
|
|
|
|
|
didChange = true;
|
|
|
|
|
this.__owl__.storeHash = storeHash;
|
2019-04-11 13:08:00 +02:00
|
|
|
}
|
2019-04-14 21:37:05 +02:00
|
|
|
} else {
|
|
|
|
|
didChange = !shallowEqual(
|
|
|
|
|
storeProps,
|
|
|
|
|
this.__owl__.currentStoreProps
|
|
|
|
|
);
|
2019-04-11 13:08:00 +02:00
|
|
|
}
|
|
|
|
|
if (didChange) {
|
2019-04-14 21:37:05 +02:00
|
|
|
this.__owl__.currentStoreProps = storeProps;
|
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) {
|
2019-04-13 14:14:34 +02:00
|
|
|
if (this.__owl__.ownProps !== nextProps) {
|
|
|
|
|
this.__owl__.currentStoreProps = mapStateToProps(
|
2019-04-08 23:02:23 +02:00
|
|
|
this.env.store.state,
|
|
|
|
|
nextProps
|
|
|
|
|
);
|
|
|
|
|
}
|
2019-04-13 14:14:34 +02:00
|
|
|
this.__owl__.ownProps = nextProps;
|
2019-04-08 23:02:23 +02:00
|
|
|
const mergedProps = Object.assign(
|
|
|
|
|
{},
|
|
|
|
|
nextProps,
|
2019-04-13 14:14:34 +02:00
|
|
|
this.__owl__.currentStoreProps
|
2019-04-08 23:02:23 +02:00
|
|
|
);
|
|
|
|
|
return super.updateProps(mergedProps, forceUpdate);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
};
|
2019-03-20 11:49:41 +01:00
|
|
|
}
|