@deephaven/golden-layout
Version:
A multi-screen javascript Layout manager
150 lines (142 loc) • 6.36 kB
JavaScript
function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import $ from 'jquery';
/**
* A specialised GoldenLayout component that binds GoldenLayout container
* lifecycle events to react components
*
* @param container
* @param state state is not required for react components
*/
export default class ReactComponentHandler {
constructor(container, state) {
_defineProperty(this, "_container", void 0);
_defineProperty(this, "_reactComponent", null);
_defineProperty(this, "_portalComponent", null);
_defineProperty(this, "_originalComponentWillUpdate", undefined);
_defineProperty(this, "_initialState", void 0);
_defineProperty(this, "_reactClass", void 0);
this._reactComponent = null;
this._portalComponent = null;
this._originalComponentWillUpdate = undefined;
this._container = container;
this._initialState = state;
this._reactClass = this._getReactClass();
this._container.on('open', this._render, this);
this._container.on('destroy', this._destroy, this);
}
/**
* Gets the unique key to use for the react component
* @returns Unique key for the component
*/
_key() {
var id = this._container._config.id;
if (!id) {
throw new Error('Cannot mount panel without id');
}
// If addId is called multiple times, an element can have multiple IDs in golden-layout
// We don't use it, but changing the type requires many changes and a separate PR
if (Array.isArray(id)) {
return id.join(',');
}
return id;
}
/**
* Creates the react class and component and hydrates it with
* the initial state - if one is present
*
* By default, react's getInitialState will be used
*
* Creates a portal so the non-react golden-layout code still works,
* but also allows us to mount the React components in the app's tree
* instead of separate sibling root trees
*/
_render() {
var key = this._key();
this._portalComponent = /*#__PURE__*/ReactDOM.createPortal(this._getReactComponent(), this._container.getElement()[0], key);
this._container.layoutManager.addReactChild(key, this._portalComponent);
}
/**
* Fired by react when the component is created.
* <p>
* Note: This callback is used instead of the return from `ReactDOM.render` because
* of https://github.com/facebook/react/issues/10309.
* </p>
*
* @param component The component instance created by the `ReactDOM.render` call in the `_render` method.
*/
_gotReactComponent(component) {
if (!component) {
return;
}
this._reactComponent = component;
// Class components manipulate the lifecycle to hook into state changes
// Functional components can save data with the usePersistentState hook
if (this._reactComponent instanceof Component) {
this._originalComponentWillUpdate = this._reactComponent.componentWillUpdate;
this._reactComponent.componentWillUpdate = this._onUpdate.bind(this);
var state = this._container.getState();
if (state) {
this._reactComponent.setState(state);
}
}
}
/**
* Removes the component from the DOM and thus invokes React's unmount lifecycle
*/
_destroy() {
this._container.layoutManager.removeReactChild(this._key(), this._portalComponent);
this._container.off('open', this._render, this);
this._container.off('destroy', this._destroy, this);
}
/**
* Hooks into React's state management and applies the componentstate
* to GoldenLayout
*/
_onUpdate(nextProps, nextState) {
var _this$_originalCompon;
this._container.setState(nextState);
(_this$_originalCompon = this._originalComponentWillUpdate) === null || _this$_originalCompon === void 0 || _this$_originalCompon.call(this._reactComponent, nextProps, nextState, undefined);
}
/**
* Retrieves the react class from GoldenLayout's registry
* @returns react class
*/
_getReactClass() {
var componentName = this._container._config.component;
if (!componentName) {
throw new Error('No react component name. type: react-component needs a field `component`');
}
var reactClass = this._container.layoutManager.getComponent(componentName) || this._container.layoutManager.getFallbackComponent();
if (!reactClass) {
throw new Error('React component "' + componentName + '" not found. ' + 'Please register all components with GoldenLayout using `registerComponent(name, component)`');
}
return reactClass;
}
/**
* Copies and extends the properties array and returns the React element
*/
_getReactComponent() {
var defaultProps = {
glEventHub: this._container.layoutManager.eventHub,
glContainer: this._container,
/**
* This ref assignment makes use of callback refs which is a legacy ref style in React.
* It uses the callback to inject GoldenLayout _onUpdate into the React componentWillUpdate lifecycle.
* This allows GoldenLayout to track the internal state of class components.
* We then emit this state change and somewhere furter up, serialize it.
* Specifically we look for state.panelState changes.
* We should not do this going forward as it's quite unclear where/why your component's state might be saved.
* This ref cannot be removed unless we refactor all existing panels to not rely on the magic of panelState.
* DashboardUtils#dehydrate is where the panelState gets read/saved.
*/
ref: this._gotReactComponent.bind(this)
};
var props = $.extend(defaultProps, this._container._config.props);
return /*#__PURE__*/React.createElement(this._reactClass, props);
}
}
//# sourceMappingURL=ReactComponentHandler.js.map