-
Notifications
You must be signed in to change notification settings - Fork 25
/
Copy pathxclass.ts
149 lines (141 loc) · 5.15 KB
/
xclass.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
import * as React from 'react';
import { createElement as h } from 'react'
import * as PropTypes from 'prop-types';
import { Plan, Xcomponent, XcomponentClass, ContextEngine, XREACT_ENGINE, Update, Actions, Xprops } from './interfaces'
import { streamOps, Stream, Subject } from './xs'
export const CONTEXT_TYPE = {
[XREACT_ENGINE]: PropTypes.shape({
intent$: PropTypes.object,
history$: PropTypes.object
})
};
function isSFC(Component: React.ComponentClass<any> | React.SFC<any>): Component is React.SFC<any> {
return (typeof Component == 'function')
}
export function extendXComponentClass<E extends Stream, I, S>(WrappedComponent: XcomponentClass<E, I, S>, main: Plan<E, I, S>): XcomponentClass<E, I, S> {
return class XNode extends WrappedComponent {
static contextTypes = CONTEXT_TYPE
static displayName = `X(${getDisplayName(WrappedComponent)})`
constructor(props: Xprops<I>, context: ContextEngine<E, I, S>) {
super(props, context);
let engine = context[XREACT_ENGINE]
let { actions, update$ } = main(engine.intent$, props)
this.machine.update$ = streamOps.merge<Update<S>, Update<S>>(this.machine.update$, update$)
if (actions)
this.machine.actions = Object.assign({}, bindActions(actions, engine.intent$, this), this.machine.actions)
}
}
}
export function genXComponentClass<E extends Stream, I, S>(WrappedComponent: React.ComponentType<S>, main: Plan<E, I, S>, opts?: any): XcomponentClass<E, I, S> {
return class XLeaf extends Xcomponent<E, I, S> {
static contextTypes = CONTEXT_TYPE
static displayName = `X(${getDisplayName(WrappedComponent)})`
defaultKeys: (keyof S)[]
constructor(props: Xprops<I>, context: ContextEngine<E, I, S>) {
super(props, context);
let engine = context[XREACT_ENGINE]
let { actions, update$ } = main(engine.intent$, props)
this.machine = {
update$: update$
}
this.machine.actions = bindActions(actions || {}, engine.intent$, this)
this.defaultKeys = WrappedComponent.defaultProps ? (<(keyof S)[]>Object.keys(WrappedComponent.defaultProps)) : [];
this.state = (Object.assign(
{},
WrappedComponent.defaultProps,
<Pick<S, keyof S>>pick(this.defaultKeys, props)
));
}
componentWillReceiveProps(nextProps: I) {
this.setState((state, props) => Object.assign({}, nextProps, pick(this.defaultKeys, state)));
}
componentDidMount() {
this.subscription = streamOps.subscribe(
this.machine.update$,
(action: Update<S>) => {
if (action instanceof Function) {
if (process.env.NODE_ENV == 'debug')
console.log('UPDATE:', action)
this.setState((prevState, props) => {
let newState: S = action.call(this, prevState, props);
this.context[XREACT_ENGINE].history$.next(newState)
if (process.env.NODE_ENV == 'debug')
console.log('STATE:', newState)
return newState;
});
} else {
/* istanbul ignore next */
console.warn(
'action',
action,
'need to be a Function which map from current state to new state'
);
}
},
() => {
this.context[XREACT_ENGINE].history$.complete(this.state)
if (process.env.NODE_ENV == 'production') {
console.error('YOU HAVE TERMINATED THE INTENT STREAM...')
}
if (process.env.NODE_ENV == 'debug') {
console.log(`LAST STATE is`, this.state)
}
}
);
}
componentWillUnmount() {
this.subscription.unsubscribe();
}
render() {
if (isSFC(WrappedComponent)) {
return h(
WrappedComponent,
Object.assign({}, opts, this.props, this.state, {
actions: this.machine.actions,
})
);
} else {
return h(
WrappedComponent,
Object.assign({}, opts, this.props, this.state, {
actions: this.machine.actions,
})
);
}
}
}
}
function getDisplayName<E extends Stream, I, S>(WrappedComponent: React.ComponentType<S>) {
return WrappedComponent.displayName || WrappedComponent.name || 'X';
}
function bindActions<E extends Stream, I, S>(actions: Actions<void>, intent$: Subject<E, I>, self: XcomponentClass<E, I, S> | Xcomponent<E, I, S>) {
let _actions: Actions<void> = {
fromEvent(e: Event) {
return intent$.next(e);
},
fromPromise(p: Promise<I>) {
return p.then(x => intent$.next(x));
},
terminate(a: I) {
if (process.env.NODE_ENV == 'debug')
console.error('INTENT TERMINATED')
return intent$.complete(a)
}
};
for (let a in actions) {
_actions[a] = (...args: any[]) => {
return intent$.next(actions[a].apply(self, args));
};
}
return _actions;
}
function pick<A>(names: Array<keyof A>, obj: A) {
let result = <Pick<A, keyof A>>{};
for (let name of names) {
if (obj[name]) result[name] = obj[name];
}
return result;
}
function isPromise(p: any): p is Promise<any> {
return p !== null && typeof p === 'object' && typeof p.then === 'function'
}