react-blessed
Version:
A react renderer for blessed.
189 lines (154 loc) • 4.93 kB
JavaScript
/**
* React Blessed Component
* ========================
*
* React component abstraction for the blessed library.
*/
import blessed from 'blessed';
import ReactMultiChild from 'react/lib/ReactMultiChild';
import ReactBlessedIDOperations from './ReactBlessedIDOperations';
import invariant from 'invariant';
import update from './update';
import solveClass from './solveClass';
import {extend, groupBy, startCase} from 'lodash';
/**
* Variable types that must be solved as content rather than real children.
*/
const CONTENT_TYPES = {string: true, number: true};
/**
* Renders the given react element with blessed.
*
* @constructor ReactBlessedComponent
* @extends ReactMultiChild
*/
export default class ReactBlessedComponent {
constructor(tag) {
this._tag = tag.toLowerCase();
this._renderedChildren = null;
this._previousStyle = null;
this._previousStyleCopy = null;
this._rootNodeID = null;
this._wrapperState = null;
this._topLevelWrapper = null;
this._nodeWithLegacyProperties = null;
}
construct(element) {
// Setting some properties
this._currentElement = element;
this._eventListener = (type, ...args) => {
const handler = this._currentElement.props['on' + startCase(type)];
if (typeof handler === 'function')
handler.apply(null, args);
};
}
/**
* Mounting the root component.
*
* @internal
* @param {string} rootID - The root blessed ID for this node.
* @param {ReactBlessedReconcileTransaction} transaction
* @param {object} context
*/
mountComponent(rootID, transaction, context) {
this._rootNodeID = rootID;
// Mounting blessed node
const node = this.mountNode(
ReactBlessedIDOperations.getParent(rootID),
this._currentElement
);
ReactBlessedIDOperations.add(rootID, node);
// Mounting children
let childrenToUse = this._currentElement.props.children;
childrenToUse = childrenToUse === null ? [] : [].concat(childrenToUse);
if (childrenToUse.length) {
// Discriminating content components from real children
const {content=null, realChildren=[]} = groupBy(childrenToUse, (c) => {
return CONTENT_TYPES[typeof c] ? 'content' : 'realChildren';
});
// Setting textual content
if (content)
node.setContent('' + content.join(''));
// Mounting real children
this.mountChildren(
realChildren,
transaction,
context
);
}
// Rendering the screen
ReactBlessedIDOperations.screen.debouncedRender();
}
/**
* Mounting the blessed node itself.
*
* @param {BlessedNode|BlessedScreen} parent - The parent node.
* @param {ReactElement} element - The element to mount.
* @return {BlessedNode} - The mounted node.
*/
mountNode(parent, element) {
const {props, type} = element,
{children, ...options} = props,
blessedElement = blessed[type];
invariant(
!!blessedElement,
`Invalid blessed element "${type}".`
);
const node = blessed[type](solveClass(options));
node.on('event', this._eventListener);
parent.append(node);
return node;
}
/**
* Receive a component update.
*
* @param {ReactElement} nextElement
* @param {ReactReconcileTransaction} transaction
* @param {object} context
* @internal
* @overridable
*/
receiveComponent(nextElement, transaction, context) {
const {props: {children, ...options}} = nextElement,
node = ReactBlessedIDOperations.get(this._rootNodeID);
update(node, solveClass(options));
// Updating children
const childrenToUse = children === null ? [] : [].concat(children);
if (childrenToUse.length) {
// Discriminating content components from real children
const {content=null, realChildren=[]} = groupBy(childrenToUse, (c) => {
return CONTENT_TYPES[typeof c] ? 'content' : 'realChildren';
});
// Setting textual content
if (content)
node.setContent('' + content.join(''));
this.updateChildren(realChildren, transaction, context);
}
ReactBlessedIDOperations.screen.debouncedRender();
}
/**
* Dropping the component.
*/
unmountComponent() {
this.unmountChildren();
const node = ReactBlessedIDOperations.get(this._rootNodeID);
node.off('event', this._eventListener);
node.destroy();
ReactBlessedIDOperations.drop(this._rootNodeID);
this._rootNodeID = null;
}
/**
* Getting a public instance of the component for refs.
*
* @return {BlessedNode} - The instance's node.
*/
getPublicInstance() {
return ReactBlessedIDOperations.get(this._rootNodeID);
}
}
/**
* Extending the component with the MultiChild mixin.
*/
extend(
ReactBlessedComponent.prototype,
ReactMultiChild.Mixin
);