├── .github
└── workflows
│ └── npm-publish.yml
├── .gitignore
├── LICENSE
├── README.md
├── index.js
├── package.json
└── src
├── ReactInstanceHandles.js
├── ReactUMGComponent.js
├── ReactUMGDefaultInjection.js
├── ReactUMGEmptyComponent.js
├── ReactUMGMount.js
├── ReactUMGNodeMap.js
├── ReactUMGReconcileTransaction.js
├── UMGRoots.js
├── components
├── ReactUMGClassMap.js
├── index.js
└── set_attrs.js
├── devtools
├── InitializeJavaScriptAppEngine.js
└── setupDevtools.js
├── editor-maker.js
└── index.js
/.github/workflows/npm-publish.yml:
--------------------------------------------------------------------------------
1 | # This workflow will run tests using node and then publish a package to GitHub Packages when a release is created
2 | # For more information see: https://docs.github.com/en/actions/publishing-packages/publishing-nodejs-packages
3 |
4 | name: Node.js Package
5 |
6 | on:
7 | release:
8 | types: [created]
9 |
10 | jobs:
11 | publish-npm:
12 | runs-on: ubuntu-latest
13 | steps:
14 | - uses: actions/checkout@v3
15 | - uses: actions/setup-node@v3
16 | with:
17 | node-version: 16
18 | registry-url: https://registry.npmjs.org/
19 | - run: npm publish
20 | env:
21 | NODE_AUTH_TOKEN: ${{secrets.npm_token}}
22 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | *.log
3 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | React-UMG
2 | ________________________________________
3 | Copyright (C) 2016 NCSOFT Corporation and other contributors. All Rights Reserved.
4 | Copyright (C) 2016 drywolf. All Rights Reserved.
5 | Copyright (C) 2016 Facebook Inc. All Rights Reserved.
6 |
7 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
8 |
9 | Permission is granted to anyone to use this software subject to acceptance and compliance with the acceptance and compliance with any other open source licenses attached below this copyright notice.
10 | The following restrictions shall also apply:
11 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
12 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
13 | 3. Neither the name of NCSOFT Corporation nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
14 |
15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
16 | ________________________________________
17 | This software uses Open Source Software (OSS). You can find the link for the source code of these open source projects, along with applicable license information, below.
18 |
19 | react-umg
20 | https://github.com/drywolf/react-umg
21 | Copyright (C) 2016 drywolf
22 | MIT License
23 | See license text at https://github.com/drywolf/react-umg/blob/master/LICENSE
24 |
25 | React Native
26 | https://github.com/facebook/react-native
27 | Copyright (C) 2016 Facebook Inc.
28 | BSD-3-Clause License
29 | See license text at https://github.com/facebook/react-native/blob/master/LICENSE
30 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # [React-UMG](https://github.com/ncsoft/React-UMG) · [](https://www.npmjs.com/package/react-umg)
2 |
3 | This repository is a fork of [react-umg](https://github.com/drywolf/react-umg) whose original author is [Wolfgang Steiner](https://github.com/drywolf)
4 |
5 | A React renderer for Unreal Motion Graphics (https://docs.unrealengine.com/latest/INT/Engine/UMG/)
6 |
7 | This project is dependent on [Unreal.js](https://github.com/ncsoft/Unreal.js)
8 |
9 | - [Link to Demo](https://github.com/ncsoft/Unreal.js-demo)
10 |
11 | ##### We recommend using React with [Babel](https://babeljs.io) to let you use JSX in your Javascript code. JSX is an extension to the Javascript language that works nicely with React.
12 |
13 | ### Install
14 | To install React-UMG with npm, run:
15 |
16 | `npm i --save react-umg`
17 |
18 | ### Web-dev like Component Naming
19 |
20 | - div(UVerticalBox)
21 | - span(UHorizontalBox)
22 | - text(UTextBlock)
23 | - img(UImage)
24 | - input(EditableText)
25 |
26 | ### Example
27 |
28 | #### Create Component
29 |
30 | ```js
31 | class MyComponent extends React.Component {
32 | constructor(props, context) {
33 | super(props, context);
34 | this.state = {text:"MyComponent"};
35 | }
36 |
37 | OnTextChanged(value) {
38 | this.setState({text: value});
39 | }
40 |
41 | render() {
42 | return (
43 |
44 | this.OnTextChanged(value)}/>
45 |
46 |
47 | )
48 | }
49 | }
50 | ```
51 |
52 | #### Draw With React-UMG
53 |
54 | ```js
55 | let widget = ReactUMG.wrap();
56 | widget.AddToViewport();
57 | return () => {
58 | widget.RemoveFromViewport();
59 | }
60 | ```
61 |
62 | - [Details](https://github.com/ncsoft/Unreal.js-demo/blob/master/Content/Scripts/demos/src/demo-react.jsx)
63 |
64 |
65 | ### License
66 | - Licensed under the BSD 3-Clause "New" or "Revised" License
67 | - see [LICENSE](https://github.com/ncsoft/React-UMG/blob/master/LICENSE) for details
68 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = require('./src')
4 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-umg",
3 | "version": "0.2.6",
4 | "description": "A React renderer for Unreal Motion Graphics With Unreal.js",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "repository": {
10 | "type": "git",
11 | "url": "https://github.com/ncsoft/React-UMG.git"
12 | },
13 | "bugs": {
14 | "url": "https://github.com/ncsoft/React-UMG/issues"
15 | },
16 | "keywords": [
17 | "unreal.js",
18 | "react-umg",
19 | "react",
20 | "umg"
21 | ],
22 | "author": {
23 | "name": "NCSOFT",
24 | "email": "crocuis@ncsoft.com",
25 | "url": "http://ncsoft.com"
26 | },
27 | "license": "MIT",
28 | "dependencies": {
29 | "fbjs": "^0.8.3",
30 | "lodash": "^4.17.4",
31 | "react": "<=15.4.2",
32 | "react-dom": "<=15.4.2"
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/ReactInstanceHandles.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2013-present, Facebook, Inc.
3 | * All rights reserved.
4 | *
5 | * This source code is licensed under the BSD-style license found in the
6 | * LICENSE file in the root directory of this source tree. An additional grant
7 | * of patent rights can be found in the PATENTS file in the same directory.
8 | *
9 | * @providesModule ReactInstanceHandles
10 | */
11 |
12 | 'use strict';
13 |
14 | var _prodInvariant = require('./reactProdInvariant');
15 |
16 | var invariant = require('fbjs/lib/invariant');
17 |
18 | var SEPARATOR = '.';
19 | var SEPARATOR_LENGTH = SEPARATOR.length;
20 |
21 | /**
22 | * Maximum depth of traversals before we consider the possibility of a bad ID.
23 | */
24 | var MAX_TREE_DEPTH = 10000;
25 |
26 | /**
27 | * Creates a DOM ID prefix to use when mounting React components.
28 | *
29 | * @param {number} index A unique integer
30 | * @return {string} React root ID.
31 | * @internal
32 | */
33 | function getReactRootIDString(index) {
34 | return SEPARATOR + index.toString(36);
35 | }
36 |
37 | /**
38 | * Checks if a character in the supplied ID is a separator or the end.
39 | *
40 | * @param {string} id A React DOM ID.
41 | * @param {number} index Index of the character to check.
42 | * @return {boolean} True if the character is a separator or end of the ID.
43 | * @private
44 | */
45 | function isBoundary(id, index) {
46 | return id.charAt(index) === SEPARATOR || index === id.length;
47 | }
48 |
49 | /**
50 | * Checks if the supplied string is a valid React DOM ID.
51 | *
52 | * @param {string} id A React DOM ID, maybe.
53 | * @return {boolean} True if the string is a valid React DOM ID.
54 | * @private
55 | */
56 | function isValidID(id) {
57 | return id === '' || id.charAt(0) === SEPARATOR && id.charAt(id.length - 1) !== SEPARATOR;
58 | }
59 |
60 | /**
61 | * Checks if the first ID is an ancestor of or equal to the second ID.
62 | *
63 | * @param {string} ancestorID
64 | * @param {string} descendantID
65 | * @return {boolean} True if `ancestorID` is an ancestor of `descendantID`.
66 | * @internal
67 | */
68 | function isAncestorIDOf(ancestorID, descendantID) {
69 | return descendantID.indexOf(ancestorID) === 0 && isBoundary(descendantID, ancestorID.length);
70 | }
71 |
72 | /**
73 | * Gets the parent ID of the supplied React DOM ID, `id`.
74 | *
75 | * @param {string} id ID of a component.
76 | * @return {string} ID of the parent, or an empty string.
77 | * @private
78 | */
79 | function getParentID(id) {
80 | return id ? id.substr(0, id.lastIndexOf(SEPARATOR)) : '';
81 | }
82 |
83 | /**
84 | * Gets the next DOM ID on the tree path from the supplied `ancestorID` to the
85 | * supplied `destinationID`. If they are equal, the ID is returned.
86 | *
87 | * @param {string} ancestorID ID of an ancestor node of `destinationID`.
88 | * @param {string} destinationID ID of the destination node.
89 | * @return {string} Next ID on the path from `ancestorID` to `destinationID`.
90 | * @private
91 | */
92 | function getNextDescendantID(ancestorID, destinationID) {
93 | !(isValidID(ancestorID) && isValidID(destinationID)) ? process.env.NODE_ENV !== 'production' ? invariant(false, 'getNextDescendantID(%s, %s): Received an invalid React DOM ID.', ancestorID, destinationID) : _prodInvariant('112', ancestorID, destinationID) : void 0;
94 | !isAncestorIDOf(ancestorID, destinationID) ? process.env.NODE_ENV !== 'production' ? invariant(false, 'getNextDescendantID(...): React has made an invalid assumption about the DOM hierarchy. Expected `%s` to be an ancestor of `%s`.', ancestorID, destinationID) : _prodInvariant('113', ancestorID, destinationID) : void 0;
95 | if (ancestorID === destinationID) {
96 | return ancestorID;
97 | }
98 | // Skip over the ancestor and the immediate separator. Traverse until we hit
99 | // another separator or we reach the end of `destinationID`.
100 | var start = ancestorID.length + SEPARATOR_LENGTH;
101 | var i;
102 | for (i = start; i < destinationID.length; i++) {
103 | if (isBoundary(destinationID, i)) {
104 | break;
105 | }
106 | }
107 | return destinationID.substr(0, i);
108 | }
109 |
110 | /**
111 | * Gets the nearest common ancestor ID of two IDs.
112 | *
113 | * Using this ID scheme, the nearest common ancestor ID is the longest common
114 | * prefix of the two IDs that immediately preceded a "marker" in both strings.
115 | *
116 | * @param {string} oneID
117 | * @param {string} twoID
118 | * @return {string} Nearest common ancestor ID, or the empty string if none.
119 | * @private
120 | */
121 | function getFirstCommonAncestorID(oneID, twoID) {
122 | var minLength = Math.min(oneID.length, twoID.length);
123 | if (minLength === 0) {
124 | return '';
125 | }
126 | var lastCommonMarkerIndex = 0;
127 | // Use `<=` to traverse until the "EOL" of the shorter string.
128 | for (var i = 0; i <= minLength; i++) {
129 | if (isBoundary(oneID, i) && isBoundary(twoID, i)) {
130 | lastCommonMarkerIndex = i;
131 | } else if (oneID.charAt(i) !== twoID.charAt(i)) {
132 | break;
133 | }
134 | }
135 | var longestCommonID = oneID.substr(0, lastCommonMarkerIndex);
136 | !isValidID(longestCommonID) ? process.env.NODE_ENV !== 'production' ? invariant(false, 'getFirstCommonAncestorID(%s, %s): Expected a valid React DOM ID: %s', oneID, twoID, longestCommonID) : _prodInvariant('114', oneID, twoID, longestCommonID) : void 0;
137 | return longestCommonID;
138 | }
139 |
140 | /**
141 | * Traverses the parent path between two IDs (either up or down). The IDs must
142 | * not be the same, and there must exist a parent path between them. If the
143 | * callback returns `false`, traversal is stopped.
144 | *
145 | * @param {?string} start ID at which to start traversal.
146 | * @param {?string} stop ID at which to end traversal.
147 | * @param {function} cb Callback to invoke each ID with.
148 | * @param {*} arg Argument to invoke the callback with.
149 | * @param {?boolean} skipFirst Whether or not to skip the first node.
150 | * @param {?boolean} skipLast Whether or not to skip the last node.
151 | * @private
152 | */
153 | function traverseParentPath(start, stop, cb, arg, skipFirst, skipLast) {
154 | start = start || '';
155 | stop = stop || '';
156 | !(start !== stop) ? process.env.NODE_ENV !== 'production' ? invariant(false, 'traverseParentPath(...): Cannot traverse from and to the same ID, `%s`.', start) : _prodInvariant('115', start) : void 0;
157 | var traverseUp = isAncestorIDOf(stop, start);
158 | !(traverseUp || isAncestorIDOf(start, stop)) ? process.env.NODE_ENV !== 'production' ? invariant(false, 'traverseParentPath(%s, %s, ...): Cannot traverse from two IDs that do not have a parent path.', start, stop) : _prodInvariant('116', start, stop) : void 0;
159 | // Traverse from `start` to `stop` one depth at a time.
160 | var depth = 0;
161 | var traverse = traverseUp ? getParentID : getNextDescendantID;
162 | for (var id = start;; /* until break */id = traverse(id, stop)) {
163 | var ret;
164 | if ((!skipFirst || id !== start) && (!skipLast || id !== stop)) {
165 | ret = cb(id, traverseUp, arg);
166 | }
167 | if (ret === false || id === stop) {
168 | // Only break //after// visiting `stop`.
169 | break;
170 | }
171 | !(depth++ < MAX_TREE_DEPTH) ? process.env.NODE_ENV !== 'production' ? invariant(false, 'traverseParentPath(%s, %s, ...): Detected an infinite loop while traversing the React DOM ID tree. This may be due to malformed IDs: %s', start, stop, id) : _prodInvariant('117', start, stop, id) : void 0;
172 | }
173 | }
174 |
175 | /**
176 | * Manages the IDs assigned to DOM representations of React components. This
177 | * uses a specific scheme in order to traverse the DOM efficiently (e.g. in
178 | * order to simulate events).
179 | *
180 | * @internal
181 | */
182 | var ReactInstanceHandles = {
183 |
184 | /**
185 | * Constructs a React root ID
186 | * @param {number} index A unique integer
187 | * @return {string} A React root ID.
188 | */
189 | createReactRootID: function (index) {
190 | return getReactRootIDString(index);
191 | },
192 |
193 | /**
194 | * Constructs a React ID by joining a root ID with a name.
195 | *
196 | * @param {string} rootID Root ID of a parent component.
197 | * @param {string} name A component's name (as flattened children).
198 | * @return {string} A React ID.
199 | * @internal
200 | */
201 | createReactID: function (rootID, name) {
202 | return rootID + name;
203 | },
204 |
205 | /**
206 | * Gets the DOM ID of the React component that is the root of the tree that
207 | * contains the React component with the supplied DOM ID.
208 | *
209 | * @param {string} id DOM ID of a React component.
210 | * @return {?string} DOM ID of the React component that is the root.
211 | * @internal
212 | */
213 | getReactRootIDFromNodeID: function (id) {
214 | if (id && id.charAt(0) === SEPARATOR && id.length > 1) {
215 | var index = id.indexOf(SEPARATOR, 1);
216 | return index > -1 ? id.substr(0, index) : id;
217 | }
218 | return null;
219 | },
220 |
221 | /**
222 | * Traverses the ID hierarchy and invokes the supplied `cb` on any IDs that
223 | * should would receive a `mouseEnter` or `mouseLeave` event.
224 | *
225 | * NOTE: Does not invoke the callback on the nearest common ancestor because
226 | * nothing "entered" or "left" that element.
227 | *
228 | * @param {string} leaveID ID being left.
229 | * @param {string} enterID ID being entered.
230 | * @param {function} cb Callback to invoke on each entered/left ID.
231 | * @param {*} upArg Argument to invoke the callback with on left IDs.
232 | * @param {*} downArg Argument to invoke the callback with on entered IDs.
233 | * @internal
234 | */
235 | traverseEnterLeave: function (leaveID, enterID, cb, upArg, downArg) {
236 | var ancestorID = getFirstCommonAncestorID(leaveID, enterID);
237 | if (ancestorID !== leaveID) {
238 | traverseParentPath(leaveID, ancestorID, cb, upArg, false, true);
239 | }
240 | if (ancestorID !== enterID) {
241 | traverseParentPath(ancestorID, enterID, cb, downArg, true, false);
242 | }
243 | },
244 |
245 | /**
246 | * Simulates the traversal of a two-phase, capture/bubble event dispatch.
247 | *
248 | * NOTE: This traversal happens on IDs without touching the DOM.
249 | *
250 | * @param {string} targetID ID of the target node.
251 | * @param {function} cb Callback to invoke.
252 | * @param {*} arg Argument to invoke the callback with.
253 | * @internal
254 | */
255 | traverseTwoPhase: function (targetID, cb, arg) {
256 | if (targetID) {
257 | traverseParentPath('', targetID, cb, arg, true, false);
258 | traverseParentPath(targetID, '', cb, arg, false, true);
259 | }
260 | },
261 |
262 | /**
263 | * Same as `traverseTwoPhase` but skips the `targetID`.
264 | */
265 | traverseTwoPhaseSkipTarget: function (targetID, cb, arg) {
266 | if (targetID) {
267 | traverseParentPath('', targetID, cb, arg, true, true);
268 | traverseParentPath(targetID, '', cb, arg, true, true);
269 | }
270 | },
271 |
272 | /**
273 | * Traverse a node ID, calling the supplied `cb` for each ancestor ID. For
274 | * example, passing `.0.$row-0.1` would result in `cb` getting called
275 | * with `.0`, `.0.$row-0`, and `.0.$row-0.1`.
276 | *
277 | * NOTE: This traversal happens on IDs without touching the DOM.
278 | *
279 | * @param {string} targetID ID of the target node.
280 | * @param {function} cb Callback to invoke.
281 | * @param {*} arg Argument to invoke the callback with.
282 | * @internal
283 | */
284 | traverseAncestors: function (targetID, cb, arg) {
285 | traverseParentPath('', targetID, cb, arg, true, false);
286 | },
287 |
288 | getFirstCommonAncestorID: getFirstCommonAncestorID,
289 |
290 | /**
291 | * Exposed for unit testing.
292 | * @private
293 | */
294 | _getNextDescendantID: getNextDescendantID,
295 |
296 | isAncestorIDOf: isAncestorIDOf,
297 |
298 | SEPARATOR: SEPARATOR
299 |
300 | };
301 |
302 | module.exports = ReactInstanceHandles;
--------------------------------------------------------------------------------
/src/ReactUMGComponent.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const ReactMultiChild = require('react-dom/lib/ReactMultiChild');
4 | const ReactCurrentOwner = require('react/lib/ReactCurrentOwner');
5 | const invariant = require('fbjs/lib/invariant');
6 | const warning = require('fbjs/lib/warning');
7 | const shallowEqual = require('fbjs/lib/shallowEqual');
8 | const UmgRoots = require('./UMGRoots');
9 | const TypeThunks = require('./components/ReactUMGClassMap');
10 |
11 | // In some cases we might not have a owner and when
12 | // that happens there is no need to inlcude "Check the render method of ...".
13 | const checkRenderMethod = () => ReactCurrentOwner.owner && ReactCurrentOwner.owner.getName()
14 | ? ` Check the render method of "${ReactCurrentOwner.owner.getName()}".` : '';
15 |
16 | /**
17 | * @constructor ReactUMGComponent
18 | * @extends ReactComponent
19 | * @extends ReactMultiChild
20 | */
21 | const ReactUMGComponent = function(element) {
22 | this.node = null;
23 | this._mountImage = null;
24 | this._renderedChildren = null;
25 | this._currentElement = element;
26 | this.ueobj = null;
27 |
28 | this._rootNodeID = null;
29 | this._typeThunk = TypeThunks[element.type];
30 |
31 | if (process.env.NODE_ENV !== 'production') {
32 | warning(
33 | Object.keys(TypeThunks).indexOf(element.type) > -1,
34 | 'Attempted to render an unsupported generic component "%s". ' +
35 | 'Must be one of the following: ' + Object.keys(TypeThunks),
36 | element.type,
37 | checkRenderMethod()
38 | );
39 | }
40 | };
41 |
42 | /**
43 | * Mixin for UMG components.
44 | */
45 | ReactUMGComponent.Mixin = {
46 | getHostNode() {},
47 |
48 | getPublicInstance() {
49 | // TODO: This should probably use a composite wrapper
50 | return this;
51 | },
52 |
53 | unmountComponent() {
54 | if (this.ueobj) {
55 | if (this._currentElement.props.$unlink) {
56 | this._currentElement.props.$unlink(this.ueobj);
57 | }
58 | for (var key in this._currentElement.props) {
59 | if (typeof this._currentElement.props[key] === 'function') {
60 | this.updateProperty(this.ueobj, null, key);
61 | }
62 | }
63 | this.ueobj.RemoveFromParent();
64 | }
65 |
66 | this.unmountChildren();
67 | this._rootNodeID = null;
68 | this.ueobj = null;
69 | },
70 | updateProperty(widget, value, key) {
71 | this._typeThunk.applyProperty(widget,value, key);
72 | },
73 | sync() {
74 | JavascriptWidget.CallSynchronizeProperties(this.ueobj)
75 | JavascriptWidget.CallSynchronizeProperties(this.ueobj.Slot)
76 | },
77 | mountComponent(
78 | transaction, // for creating/updating
79 | rootID, // Root ID of this subtree
80 | hostContainerInfo, // nativeContainerInfo
81 | context // secret context, shhhh
82 | ) {
83 | var parent = rootID;
84 |
85 | rootID = typeof rootID === 'object' ? rootID._rootNodeID : rootID;
86 | this._rootNodeID = rootID;
87 |
88 | var umgRoot = parent.ueobj ? parent.ueobj : UmgRoots[rootID];
89 | if (umgRoot instanceof JavascriptWidget) {
90 | umgRoot = umgRoot.WidgetTree.RootWidget
91 | }
92 | var outer = Root.GetEngine ? JavascriptLibrary.CreatePackage(null,'/Script/Javascript') : GWorld
93 |
94 | this.ueobj = this._typeThunk.createUmgElement(
95 | this._currentElement,
96 | cls => {
97 | var widget = new cls(outer);
98 | var props = this._currentElement.props;
99 | for (var key in props) {
100 | this.updateProperty(widget, props[key], key);
101 | }
102 | if (widget instanceof JavascriptWidget) {
103 | widget.AddChild(new SizeBox(outer));
104 | }
105 | if (umgRoot['AddChild'] != null) {
106 | var slot = umgRoot.AddChild(widget);
107 | if (slot)
108 | slot.Content.Slot = slot;
109 | return widget;
110 | }
111 | else {
112 | console.error('cannot add child', umgRoot);
113 | }
114 | }
115 | );
116 |
117 | this.sync();
118 |
119 | if (this._currentElement.props.$link) {
120 | this._currentElement.props.$link(this.ueobj);
121 | }
122 | this.initializeChildren(
123 | this._currentElement.props.children,
124 | transaction,
125 | context
126 | );
127 | return rootID;
128 | },
129 |
130 | /**
131 | * Updates the component's currently mounted representation.
132 | */
133 | receiveComponent(
134 | nextElement, transaction, context) {
135 | const prevElement = this._currentElement;
136 | this._currentElement = nextElement;
137 | this.updateComponent(transaction, prevElement, nextElement, context);
138 | },
139 | updateComponent(
140 | transaction, prevElement, nextElement, context) {
141 | var lastProps = prevElement.props;
142 | var nextProps = nextElement.props;
143 | if (!shallowEqual(lastProps, nextProps)) {
144 | this.updateProperties(lastProps, nextProps, transaction)
145 | }
146 | this.updateChildren(nextProps.children, transaction, context);
147 | },
148 | updateProperties(
149 | lastProps, nextProps, transaction) {
150 | for (var propKey in nextProps) {
151 | var nextProp = nextProps[propKey];
152 | var lastProp = lastProps != null ? lastProps[propKey] : undefined;
153 | if (!nextProps.hasOwnProperty(propKey) ||
154 | nextProp === lastProp ||
155 | nextProp == null && lastProp == null) {
156 | continue;
157 | }
158 | this.updateProperty(this.ueobj, nextProp, propKey);
159 | }
160 | },
161 | initializeChildren(
162 | children, transaction, context) {
163 | this.mountChildren(children, transaction, context);
164 | },
165 | };
166 |
167 | /**
168 | * Order of mixins is important. ReactUMGComponent overrides methods in
169 | * ReactMultiChild.
170 | */
171 | Object.assign(
172 | ReactUMGComponent.prototype,
173 | ReactMultiChild.Mixin,
174 | ReactUMGComponent.Mixin
175 | );
176 |
177 | module.exports = ReactUMGComponent;
178 |
--------------------------------------------------------------------------------
/src/ReactUMGDefaultInjection.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /**
4 | * React UMG Default Injection
5 | */
6 | require('./devtools/InitializeJavaScriptAppEngine');
7 | const ReactInjection = require('react-dom/lib/ReactInjection');
8 | const ReactDefaultBatchingStrategy = require('react-dom/lib/ReactDefaultBatchingStrategy');
9 | const ReactComponentEnvironment = require('react-dom/lib/ReactComponentEnvironment');
10 | const ReactUMGReconcileTransaction = require('./ReactUMGReconcileTransaction');
11 | const ReactUMGComponent = require('./ReactUMGComponent');
12 | const ReactUMGEmptyComponent = require('./ReactUMGEmptyComponent');
13 | var alreadyInjected = false;
14 |
15 | function inject() {
16 | if (alreadyInjected) {
17 | // TODO: This is currently true because these injections are shared between
18 | // the client and the server package. They should be built independently
19 | // and not share any injection state. Then this problem will be solved.
20 | return;
21 | }
22 | alreadyInjected = true;
23 |
24 | ReactInjection.HostComponent.injectGenericComponentClass(
25 | ReactUMGComponent
26 | );
27 |
28 | // // Maybe?
29 | ReactInjection.HostComponent.injectTextComponentClass(
30 | function(instantiate) {return new ReactUMGEmptyComponent(instantiate)}
31 | );
32 |
33 | ReactInjection.Updates.injectReconcileTransaction(
34 | ReactUMGReconcileTransaction
35 | );
36 |
37 | ReactInjection.Updates.injectBatchingStrategy(
38 | ReactDefaultBatchingStrategy
39 | );
40 |
41 | ReactInjection.EmptyComponent.injectEmptyComponentFactory(
42 | function(instantiate){ return new ReactUMGEmptyComponent(instantiate) }
43 | );
44 |
45 | ReactComponentEnvironment.processChildrenUpdates = function() {};
46 | ReactComponentEnvironment.replaceNodeWithMarkup = function() {};
47 | ReactComponentEnvironment.unmountIDFromEnvironment = function() {};
48 | }
49 |
50 | module.exports = inject
--------------------------------------------------------------------------------
/src/ReactUMGEmptyComponent.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const ReactMultiChild = require('react-dom/lib/ReactMultiChild');
4 |
5 | const ReactUMGEmptyComponent = function(element) {
6 |
7 | this.node = null;
8 | this._mountImage = null;
9 | this._renderedChildren = null;
10 | this._currentElement = element;
11 | this._rootNodeID = null;
12 | };
13 |
14 | ReactUMGEmptyComponent.prototype = Object.assign(
15 | {
16 | construct(element) {},
17 |
18 | getPublicInstance() {},
19 | mountComponent() {},
20 | receiveComponent() {},
21 | unmountComponent() {},
22 | // Implement both of these for now. React <= 15.0 uses getNativeNode, but
23 | // that is confusing. Host environment is more accurate and will be used
24 | // going forward
25 | getNativeNode() {},
26 | getHostNode() {}
27 | },
28 | ReactMultiChild.Mixin
29 | );
30 |
31 | module.exports = ReactUMGEmptyComponent;
32 |
--------------------------------------------------------------------------------
/src/ReactUMGMount.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const ReactElement = require('react/lib/ReactElement');
4 | const ReactInstanceMap = require('react-dom/lib/ReactInstanceMap');
5 | const ReactUpdates = require('react-dom/lib/ReactUpdates');
6 | const ReactUpdateQueue = require('react-dom/lib/ReactUpdateQueue');
7 | const ReactReconciler = require('react-dom/lib/ReactReconciler');
8 | const shouldUpdateReactComponent = require('react-dom/lib/shouldUpdateReactComponent');
9 | const instantiateReactComponent = require('react-dom/lib/instantiateReactComponent');
10 |
11 | const invariant = require('fbjs/lib/invariant');
12 | const warning = require('fbjs/lib/warning');
13 |
14 | const ReactInstanceHandles = require('./ReactInstanceHandles');
15 | const ReactUMGDefaultInjection = require('./ReactUMGDefaultInjection');
16 |
17 | ReactUMGDefaultInjection();
18 | // TODO: externalize management of UMG node meta-data (id, component, ...)
19 | let idCounter = 1;
20 |
21 | const UmgRoots = require('./UMGRoots');
22 | const TypeThunks = require('./components/ReactUMGClassMap');
23 | const NodeMap = require('./ReactUMGNodeMap');
24 |
25 | function isString(x) {
26 | return Object.prototype.toString.call(x) === "[object String]"
27 | }
28 |
29 | const ReactUMGMount = {
30 | // for react devtools
31 | _instancesByReactRootID: {},
32 | nativeTagToRootNodeID(nativeTag) {
33 | throw new Error('TODO: implement nativeTagToRootNodeID ' + nativeTag);
34 | },
35 |
36 | /**
37 | * Renders a React component to the supplied `container` port.
38 | *
39 | * If the React component was previously rendered into `container`, this will
40 | * perform an update on it and only mutate the pins as necessary to reflect
41 | * the latest React component.
42 | */
43 | render(
44 | nextElement,
45 | umgWidget,
46 | callback
47 | ) {
48 | // WIP: it appears as though nextElement.props is an empty object...
49 | invariant(
50 | ReactElement.isValidElement(nextElement),
51 | 'ReactUMG.render(): Invalid component element.%s',
52 | (
53 | typeof nextElement === 'function' ?
54 | ' Instead of passing a component class, make sure to instantiate ' +
55 | 'it by passing it to React.createElement.' :
56 | // Check if it quacks like an element
57 | nextElement != null && nextElement.props !== undefined ?
58 | ' This may be caused by unintentionally loading two independent ' +
59 | 'copies of React.' :
60 | ''
61 | )
62 | );
63 | if (umgWidget) {
64 | const prevComponent = umgWidget.component;
65 | if (prevComponent) {
66 | const prevWrappedElement = prevComponent._currentElement;
67 | const prevElement = prevWrappedElement.props;
68 | if (shouldUpdateReactComponent(prevElement, nextElement)) {
69 | const publicInst = prevComponent._renderedComponent.getPublicInstance();
70 | const updatedCallback = callback && function() {
71 | if (callback) {
72 | callback.call(publicInst);
73 | }
74 | };
75 |
76 | ReactUMGMount._updateRootComponent(
77 | prevComponent,
78 | nextElement,
79 | container,
80 | updatedCallback
81 | );
82 | return publicInst;
83 | } else {
84 | warning(
85 | true,
86 | 'Unexpected `else` branch in ReactUMG.render()'
87 | );
88 | }
89 | }
90 | }
91 | if (!umgWidget.reactUmgId)
92 | umgWidget.reactUmgId = idCounter++;
93 |
94 | const rootId = ReactInstanceHandles.createReactRootID(umgWidget.reactUmgId);
95 |
96 | let umgRoot = UmgRoots[rootId];
97 | if (!umgRoot) {
98 | let type = nextElement.type;
99 | // pure react component
100 | if (isString(type) == false) {
101 | type = 'uSizeBox'
102 | }
103 | let typeThunk = TypeThunks[type];
104 | let outer = Root.GetEngine ? JavascriptLibrary.CreatePackage(null,'/Script/Javascript') : GWorld;
105 | umgRoot = typeThunk.createUmgElement(nextElement, cls => new cls(outer));
106 | umgWidget.AddChild(umgRoot);
107 |
108 | UmgRoots[rootId] = umgRoot;
109 | }
110 | const nextComponent = instantiateReactComponent(nextElement);
111 |
112 | if (!umgWidget.component) {
113 | umgWidget.component = nextComponent;
114 | }
115 |
116 | ReactUpdates.batchedUpdates(() => {
117 | // Two points to React for using object pooling internally and being good
118 | // stewards of garbage collection and memory pressure.
119 | const transaction = ReactUpdates.ReactReconcileTransaction.getPooled();
120 | transaction.perform(() => {
121 | // The `component` here is an instance of your
122 | // `ReactCustomRendererComponent` class. To be 100% honest, I’m not
123 | // certain if the method signature is enforced by React core or if it is
124 | // renderer specific. This is following the ReactDOM renderer. The
125 | // important piece is that we pass our transaction and rootId through, in
126 | // addition to any other contextual information needed.
127 |
128 | nextComponent.mountComponent(
129 | transaction,
130 | rootId,
131 | // TODO: what is _idCounter used for and when should it be nonzero?
132 | {_idCounter: 0},
133 | {}
134 | );
135 | if (callback) {
136 | callback(nextComponent.getPublicInstance());
137 | }
138 | });
139 | ReactUpdates.ReactReconcileTransaction.release(transaction);
140 | });
141 |
142 | // needed for react-devtools
143 | ReactUMGMount._instancesByReactRootID[rootId] = nextComponent;
144 | NodeMap.set(nextComponent, umgWidget);
145 |
146 | umgWidget.JavascriptContext = Context;
147 | umgWidget.proxy = {
148 | OnDestroy: (bReleaseChildren) => {
149 | if (nextComponent.getPublicInstance()) {
150 | ReactUMGMount.unmountComponent(nextComponent.getPublicInstance())
151 | }
152 | }
153 | }
154 |
155 | return nextComponent.getPublicInstance();
156 | },
157 |
158 | /**
159 | * Take a component that’s already mounted and replace its props
160 | */
161 | _updateRootComponent(
162 | prevComponent, // component instance already in the DOM
163 | nextElement, // component instance to render
164 | container, // firmata connection port
165 | callback // function triggered on completion
166 | ) {
167 | ReactUpdateQueue.enqueueElementInternal(prevComponent, nextElement);
168 | if (callback) {
169 | ReactUpdateQueue.enqueueCallbackInternal(prevComponent, callback);
170 | }
171 |
172 | return prevComponent;
173 | },
174 |
175 | renderComponent(
176 | rootID,
177 | container,
178 | nextComponent,
179 | nextElement,
180 | board, // Firmata instnace
181 | callback
182 | ) {
183 |
184 | const component = nextComponent || instantiateReactComponent(nextElement);
185 |
186 | // The initial render is synchronous but any updates that happen during
187 | // rendering, in componentWillMount or componentDidMount, will be batched
188 | // according to the current batching strategy.
189 | ReactUpdates.batchedUpdates(() => {
190 | // Batched mount component
191 | const transaction = ReactUpdates.ReactReconcileTransaction.getPooled();
192 | transaction.perform(() => {
193 |
194 | component.mountComponent(
195 | transaction,
196 | rootID,
197 | {_idCounter: 0},
198 | {}
199 | );
200 | if (callback) {
201 | const publicInst = component.getPublicInstance();
202 | callback(publicInst);
203 | }
204 | });
205 | ReactUpdates.ReactReconcileTransaction.release(transaction);
206 | });
207 |
208 | return component.getPublicInstance();
209 | },
210 | unmountComponent(publicInstance) {
211 | const internalInstance = ReactUMGMount.getInternalInstance(publicInstance);
212 | let widget = NodeMap.get(internalInstance);
213 | if (widget) {
214 | const rootId = ReactInstanceHandles.createReactRootID(widget.reactUmgId);
215 | delete UmgRoots[rootId];
216 | delete ReactUMGMount._instancesByReactRootID[rootId];
217 | NodeMap.delete(internalInstance);
218 | }
219 |
220 | internalInstance.unmountComponent();
221 | },
222 | getInternalInstance(publicInstance) {
223 | // Reverse of ReactCompositeComponent(Wrapper).getPublicInstance
224 | let internalInstance = ReactInstanceMap.get(publicInstance);
225 | if (!internalInstance) {
226 | // Reverse of ReactUMGComponent.getPublicInstance
227 | internalInstance = publicInstance;
228 | }
229 | return internalInstance;
230 | },
231 | findNode(publicInstance) {
232 | const internalInstance = ReactUMGMount.getInternalInstance(publicInstance)
233 | return internalInstance && NodeMap.get(internalInstance);
234 | },
235 | wrap(nextElement, outer = Root.GetEngine ? JavascriptLibrary.CreatePackage(null,'/Script/Javascript') : GWorld) {
236 | let widget = Root.GetEngine ? new JavascriptWidget(outer) : GWorld.CreateWidget(JavascriptWidget);
237 | let publicInstance = ReactUMGMount.render(nextElement, widget);
238 | return ReactUMGMount.findNode(publicInstance);
239 | }
240 | };
241 |
242 | module.exports = ReactUMGMount;
243 |
--------------------------------------------------------------------------------
/src/ReactUMGNodeMap.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = new Map();
4 |
--------------------------------------------------------------------------------
/src/ReactUMGReconcileTransaction.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var CallbackQueue = require('react-dom/lib/CallbackQueue');
4 | var PooledClass = require('react/lib/PooledClass');
5 | var Transaction = require('react-dom/lib/Transaction');
6 | var ReactUpdateQueue = require('react-dom/lib/ReactUpdateQueue');
7 |
8 | /**
9 | * Provides a `CallbackQueue` queue for collecting `onDOMReady` callbacks during
10 | * the performing of the transaction.
11 | */
12 | var ON_UMG_READY_QUEUEING = {
13 | /**
14 | * Initializes the internal firmata `connected` queue.
15 | */
16 | initialize: function() {
17 | this.reactMountReady.reset();
18 | },
19 |
20 | /**
21 | * After Hardware is connected, invoke all registered `ready` callbacks.
22 | */
23 | close: function() {
24 | this.reactMountReady.notifyAll();
25 | },
26 | };
27 |
28 | /**
29 | * Executed within the scope of the `Transaction` instance. Consider these as
30 | * being member methods, but with an implied ordering while being isolated from
31 | * each other.
32 | */
33 | var TRANSACTION_WRAPPERS = [ON_UMG_READY_QUEUEING];
34 |
35 | function ReactUMGReconcileTransaction() {
36 | this.reinitializeTransaction();
37 | this.reactMountReady = CallbackQueue.getPooled(null);
38 | }
39 |
40 | const Mixin = {
41 | /**
42 | * @see Transaction
43 | * @abstract
44 | * @final
45 | * @return {array