UNPKG

10.8 kBJavaScriptView Raw
1'use strict';
2
3Object.defineProperty(exports, "__esModule", {
4 value: true
5});
6
7var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
8
9function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
10
11function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
12
13function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
14
15var React = require('react');
16var ReactDOM = require('react-dom');
17var ContextBridge = require('./ContextBridge').ContextBridge;
18var importedExtensionStore = require('./ExtensionStore.js').instance;
19var importedResourceLoadTracker = require('./ResourceLoadTracker').instance;
20
21/**
22 * Renderer for react component extensions for which other plugins can provide an implementing Component.
23 */
24
25var ExtensionRenderer = function (_React$Component) {
26 _inherits(ExtensionRenderer, _React$Component);
27
28 function ExtensionRenderer() {
29 _classCallCheck(this, ExtensionRenderer);
30
31 // Initial state is empty. See the componentDidMount and render functions.
32 var _this = _possibleConstructorReturn(this, (ExtensionRenderer.__proto__ || Object.getPrototypeOf(ExtensionRenderer)).call(this));
33
34 _this.state = { extensions: null };
35 return _this;
36 }
37
38 _createClass(ExtensionRenderer, [{
39 key: 'componentWillMount',
40 value: function componentWillMount() {
41 this._setExtensions(this.props);
42 }
43 }, {
44 key: 'componentDidMount',
45 value: function componentDidMount() {
46 ExtensionRenderer.resourceLoadTracker.onMount(this.props.extensionPoint);
47 this._renderAllExtensions();
48 }
49 }, {
50 key: 'componentWillUpdate',
51 value: function componentWillUpdate(nextProps, nextState) {
52 if (!nextState.extensions) {
53 this._setExtensions(nextProps);
54 }
55 }
56 }, {
57 key: 'componentDidUpdate',
58 value: function componentDidUpdate() {
59 this._renderAllExtensions();
60 }
61 }, {
62 key: 'componentWillUnmount',
63 value: function componentWillUnmount() {
64 this._unmountAllExtensions();
65 }
66
67 /**
68 * Force a reload and re-render of all extensions registered with this ExtensionPoint.
69 * Useful if the props (such as 'filter') have changed and need to be re-evaluated.
70 */
71
72 }, {
73 key: 'reloadExtensions',
74 value: function reloadExtensions() {
75 // triggers a reload of extensions via componentWillUpdate
76 this.setState({
77 extensions: null
78 });
79 }
80 }, {
81 key: '_setExtensions',
82 value: function _setExtensions() {
83 var _this2 = this;
84
85 ExtensionRenderer.extensionStore.getExtensions(this.props.extensionPoint, this.props.filter, function (extensions) {
86 return _this2.setState({ extensions: extensions });
87 });
88 }
89
90 /**
91 * This method renders the "leaf node" container divs, one for each registered extension, that live in the same
92 * react hierarchy as the &lt;ExtensionRenderer&gt; instance itself. As far as our react is concerned, these are
93 * childless divs that are never updated. Actually rendering the extensions themselves is done by
94 * _renderAllExtensions.
95 */
96
97 }, {
98 key: 'render',
99 value: function render() {
100 var extensions = this.state.extensions;
101
102 if (!extensions) {
103 // Rendered before extension data is available - if data is loaded but no
104 // extensions are found, we will get [] rather than null, and will want to
105 // render an empty wrappingElement, or possibly "default" children
106 return null;
107 }
108
109 var newChildren = [];
110
111 // Add a <div> for each of the extensions. See the __renderAllExtensions function.
112 for (var i = 0; i < extensions.length; i++) {
113 newChildren.push(React.createElement('div', { key: i }));
114 }
115
116 if (newChildren.length === 0 && this.props.children) {
117 newChildren = this.props.children;
118 }
119
120 var _props = this.props,
121 className = _props.className,
122 extensionPoint = _props.extensionPoint,
123 wrappingElement = _props.wrappingElement;
124
125
126 var classNames = ['ExtensionPoint', extensionPoint.replace(/\.+/g, '-')];
127
128 if (className) {
129 classNames.push(className);
130 }
131
132 var newProps = {
133 className: classNames.join(' ')
134 };
135
136 return React.createElement(wrappingElement, newProps, newChildren);
137 }
138
139 /**
140 * For each extension, we have created a "leaf node" element in the DOM. This method creates a new react hierarchy
141 * for each, and instructs it to render. From that point on we have a separation that keeps the main app insulated
142 * from any plugin issues that may cause react to throw while updating. Inspired by Nylas N1.
143 */
144
145 }, {
146 key: '_renderAllExtensions',
147 value: function _renderAllExtensions() {
148 var extensions = this.state.extensions;
149
150 if (!extensions || extensions.length === 0) {
151 // No extensions loaded. Return early because we may have default DOM children.
152 return;
153 }
154
155 // NB: This needs to be a lot cleverer if the list of extensions for a specific point can change;
156 // We will need to link each extension with its containing element, in some way that doesn't leak :) Easy in
157 // browsers with WeakMap, less so otherwise.
158 var el = ReactDOM.findDOMNode(this);
159 if (el) {
160 var children = el.children;
161 if (children) {
162
163 // The number of children should be exactly the same as the number
164 // of extensions. See the render function for where these are added.
165 if (extensions.length !== children.length) {
166 console.error('Unexpected error in Jenkins ExtensionRenderer rendering (' + this.props.extensionPoint + '). Expecting a child DOM node for each extension point.');
167 return;
168 }
169 // render each extension on the allocated child node.
170 for (var i = 0; i < extensions.length; i++) {
171 this._renderExtension(children[i], extensions[i]);
172 }
173 }
174 }
175 }
176
177 /** Actually render an individual extension */
178
179 }, {
180 key: '_renderExtension',
181 value: function _renderExtension(element, extension) {
182 var component = React.createElement(extension, this.props);
183 try {
184 var contextValuesAsProps = {
185 config: this.context.config,
186 router: this.context.router
187 };
188 var bridgedComponent = React.createElement(ContextBridge, contextValuesAsProps, component);
189 ReactDOM.render(bridgedComponent, element);
190 } catch (e) {
191 console.log("error rendering", extension.name, e);
192
193 var errorDiv = React.createElement(
194 'div',
195 { className: 'error alien' },
196 'Error rendering ',
197 extension.name,
198 ': ',
199 e.toString()
200 );
201 ReactDOM.render(errorDiv, element);
202 }
203 }
204
205 /**
206 * Clean up child extensions' react hierarchies. Necessary because they live in their own react hierarchies that
207 * would otherwise not be notified when this is being unmounted.
208 */
209
210 }, {
211 key: '_unmountAllExtensions',
212 value: function _unmountAllExtensions() {
213
214 var extensions = this.state.extensions;
215
216 if (!extensions || extensions.length === 0) {
217 // No extensions loaded. Return early because we may have default DOM children which react
218 // will unmount normally
219 return;
220 }
221
222 var thisNode = ReactDOM.findDOMNode(this);
223 var children = thisNode ? thisNode.children : null;
224 if (children && children.length) {
225 for (var i = 0; i < children.length; i++) {
226 var child = children[i];
227 try {
228 if (child) {
229 ReactDOM.unmountComponentAtNode(child);
230 }
231 } catch (err) {
232 // Log and continue, don't want to stop unmounting children
233 console.log("Error unmounting component", child, err);
234 }
235 }
236 }
237 }
238 }]);
239
240 return ExtensionRenderer;
241}(React.Component);
242
243exports.default = ExtensionRenderer;
244
245
246ExtensionRenderer.defaultProps = {
247 wrappingElement: "div"
248};
249
250ExtensionRenderer.propTypes = {
251 children: React.PropTypes.node,
252 extensionPoint: React.PropTypes.string.isRequired,
253 filter: React.PropTypes.any,
254 wrappingElement: React.PropTypes.oneOfType([React.PropTypes.string, React.PropTypes.element])
255};
256
257ExtensionRenderer.contextTypes = {
258 router: React.PropTypes.object,
259 config: React.PropTypes.object
260};
\No newline at end of file