1 | import {IS_SERVER} from "@gongt/ts-stl-library/check-environment";
|
2 | import {createLogger} from "@gongt/ts-stl-library/debug/create-logger";
|
3 | import {LOG_LEVEL} from "@gongt/ts-stl-library/debug/levels";
|
4 | import {GlobalVariable} from "@gongt/ts-stl-library/pattern/global-page-data";
|
5 | import * as React from "react";
|
6 | import {render} from "react-dom";
|
7 | import {inspect} from "util";
|
8 | import {GlobalContextProvider, ReactEmptyWrapper} from "./global-context";
|
9 |
|
10 | export interface IContextCreate {
|
11 | (window: Window): GlobalVariable;
|
12 | }
|
13 |
|
14 | export const REACT_ROOT_ELEMENT_ID_VARNAME = 'REACT_ROOT_ELEMENT_ID';
|
15 |
|
16 | export interface WrapComponent {
|
17 | componentName: string;
|
18 | Component: React.ComponentClass<any>;
|
19 | props: Function;
|
20 | }
|
21 |
|
22 | export type MainAppCreator = (global: GlobalVariable) => React.ReactElement<any>;
|
23 |
|
24 | export abstract class ReactRenderBase {
|
25 | protected _getComponent: MainAppCreator;
|
26 | protected wrapStack: WrapComponent[] = [];
|
27 |
|
28 | setMainApp(component: MainAppCreator) {
|
29 | this._getComponent = component;
|
30 | }
|
31 |
|
32 | protected getComponent(global: GlobalVariable): React.ReactElement<any> {
|
33 | if (!this._getComponent) {
|
34 | throw new TypeError(this.constructor.name + ': no main app.')
|
35 | }
|
36 | let component = this._getComponent(global);
|
37 | this.wrapStack.forEach((props) => {
|
38 | if (props) {
|
39 | component = <ReactEmptyWrapper {...props}>{component}</ReactEmptyWrapper>;
|
40 | } else {
|
41 | const Component = props.Component;
|
42 | component = <Component key={props.componentName}>{component}</Component>
|
43 | }
|
44 | });
|
45 |
|
46 | return component;
|
47 | }
|
48 |
|
49 | wrapComponent<P>(componentName: string, Component: React.ComponentClass<P>, props?: (global: GlobalVariable) => P) {
|
50 | this.wrapStack.push({componentName, Component, props});
|
51 | }
|
52 |
|
53 | abstract render(arg?: GlobalVariable): any;
|
54 | }
|
55 |
|
56 | export class ReactRender extends ReactRenderBase {
|
57 | constructor() {
|
58 | super();
|
59 | }
|
60 |
|
61 | render(global: GlobalVariable = new GlobalVariable()) {
|
62 | if (!global.has(REACT_ROOT_ELEMENT_ID_VARNAME)) {
|
63 | global.set(REACT_ROOT_ELEMENT_ID_VARNAME, 'react-root-random-' + (Math.random() * 10000).toFixed(0));
|
64 | }
|
65 |
|
66 | const react_root_id = global.get(REACT_ROOT_ELEMENT_ID_VARNAME);
|
67 |
|
68 | let root = document.getElementById(react_root_id);
|
69 | if (!root) {
|
70 | root = document.createElement('DIV');
|
71 | root.id = react_root_id;
|
72 | document.body.appendChild(root);
|
73 | }
|
74 |
|
75 | const component = this.getComponent(global);
|
76 |
|
77 | if (debugComponentEnabled) {
|
78 | debugComponent(component);
|
79 | }
|
80 |
|
81 | render(
|
82 | <GlobalContextProvider children={component} global={global}/>,
|
83 | root,
|
84 | );
|
85 | }
|
86 | }
|
87 |
|
88 | const debug = createLogger(LOG_LEVEL.SILLY, 'react-render');
|
89 | const warn = createLogger(LOG_LEVEL.WARN, 'react-render');
|
90 |
|
91 | export function debugComponent(comp: React.ReactElement<any>) {
|
92 | let itr = comp;
|
93 | debug('ReactRender:');
|
94 | while (itr && itr['type']) {
|
95 | let Component = itr['type'];
|
96 | if (Component === ReactEmptyWrapper) {
|
97 | Component = itr['props'].Component;
|
98 | if (!Component) {
|
99 | warn('ReactEmptyWrapper without children: ', itr);
|
100 | itr = itr['props'].children;
|
101 | continue;
|
102 | }
|
103 | }
|
104 | const name = Component['displayName'] || Component['name'] || Component;
|
105 | const obj = Object.assign({}, itr.props);
|
106 | const dbg = [];
|
107 | wellknownProps.forEach((item) => {
|
108 | if (obj[item]) {
|
109 | delete obj[item];
|
110 | dbg.push(pgreen);
|
111 | } else {
|
112 | dbg.push(pred);
|
113 | }
|
114 | dbg.push(item);
|
115 | dbg.push(preset);
|
116 | });
|
117 |
|
118 | let ins: any;
|
119 | if (IS_SERVER) {
|
120 | ins = inspect(obj, false, 1, true).replace(/\s+/g, ' ');
|
121 | if (process.stdout['columns']) {
|
122 | ins = ins.substr(0, process.stdout['columns']);
|
123 | }
|
124 | } else {
|
125 | ins = obj;
|
126 | }
|
127 | debug(messageStr, name, ...dbg, ins);
|
128 | if (!itr['props']) {
|
129 | break;
|
130 | }
|
131 | itr = itr['props'].children;
|
132 | }
|
133 | }
|
134 |
|
135 | const pred = IS_SERVER? '\x1B[38;5;9m' : 'color:red';
|
136 | const pgreen = IS_SERVER? '\x1B[38;5;10m' : 'color:lime';
|
137 | const preset = IS_SERVER? '\x1B[0m' : 'color:black';
|
138 |
|
139 | const wellknownProps = ['global', 'store', 'children', 'Component'];
|
140 | const pcolor = IS_SERVER? '%s' : '%c';
|
141 | const messagePart = wellknownProps.map(e => pcolor + '%s' + pcolor).join(', ');
|
142 | const messageStr = IS_SERVER
|
143 | ? '> %s (' + messagePart + ')\n%s'
|
144 | : '> %s (' + messagePart + ', %O)';
|
145 |
|
146 | export const debugComponentEnabled = debug.enabled;
|
147 |
|
\ | No newline at end of file |