bPanel

bPanel

  • Getting Started
  • About
  • Developers
  • Showcase

›API

Plugin Development

  • Intro to Plugin Development
  • Blockchain API

Guides

  • Node Info View

API

  • metadata
  • decorate
  • mapComponentState/Dispatch
  • getProps
  • reducers
  • middleware
  • Constants
  • Sockets
  • Decorate Plugins
  • Bundling Plugins
  • bPanel Utils

bPanel UI

  • Introduction
  • Components
  • Utilities

Theming (Skins)

  • Getting Started
  • Theming Defaults
  • Theming Variables

Decorate Plugins

Overview

Decorating plugins, and creating plugins that can be decorated, is a more advanced use case of the plugin system. The best way to think of this is as plugins or views that accept "widgets".

There are three parts to decorating plugins:

  • Have a plugin that exposes itself to decoration
  • Expose hooks as properties in your target plugin that widgets can hook into
  • Export a decoratePlugin method from your widget that targets the plugin you want to decorate

This enables for far more customizability within views. For example, you can have advanced Dashboard views where you compose a wide array of visualizations that can be used to get information about your node at a glance. Some users might want to focus on node/network performance while others might show information on transaction volume. By customizing the view with different widgets, you can have one dashboard that addresses both.

decorator

This needs to be exposed by the target plugin. The function signature is similar to the component decorator, except the first argument is a function called pluginDecorator that applies the widget decoration for each plugin that targets it. The second argument is an object with a React and PropTypes property, both of which should be passed to the pluginDecorator when it is called in decorator so that the widgets have access to them.

Let's create a quick example of a sample plugin called sample, that will create a new view from a component MyView and allow itself to be decorated by widgets.

// Import your component here. Can be any valid React component
import MyView from './MyView';

// we are going to cache a version of our view that can be updated by our decorator further down
let _MyView = MyView;

export const decorator = (pluginDecorator, { React, PropTypes }) => {
  _MyView = pluginDecorator(_DecoratedDashboard, {
    React,
    PropTypes
  });
};

export const decoratePanel = (Panel, { React, PropTypes }) => {
  return class extends React.Component {
    static displayName() {
      return 'my view'
    }

    static get propTypes() {
      return {
        customChildren: PropTypes.array
      };
    }

    render() {
     const { customChildren = [] } = this.props;
     const routeData = {
       metadata,
       Component: _MyView // passing decorated component
     };
     return (
       <Panel
         {...this.props}
         customChildren={customChildren.concat(routeData)}
       />
     );
   }
  }
}

Note that decoratePanel is exactly the same as normal except that we are passing the decorated version of the component to the route view (_MyView). Read more about decoratePanel here.

Adding hooks

The last thing you need do is add the "hooks" in your component for widgets to be able to decorate the component. This works the same as normal decoration. All that you need is some kind of customChildren prop that is displayed in your component.

import React from 'react';
import PropTypes from 'prop-types';

export default class MyView extends React.PureComponent {
  constructor(props) {
    super(props);
  }

  static get propTypes() {
    customChildrenBefore: PropTypes.node,
    customChildren: PropTypes.node
  }

  render() {
    { customChildrenBefore, customChildren } = this.props;

    return (
      <div>
        {customChildrenBefore}
        Hello World!
        {customChildren}
      </div>
    )
  }
}

If you expect widgets to decorate by overwriting the existing property, then the above is fine. Sometimes though you might want to show an array of widgets. This can be done with overwriting too (by just making sure to include the existing children in your new children property), but this can be difficult with nesting, especially for styling.

The widgetCreator actually assumes an array of functions that return React.createElement elements. Below is an example from @bpanel/dashboard plugin for a decorator hook in the target component to handle these cases.

export default class Dashboard extends React.PureComponent {
  ...
  render() {
    return(
      <div>
        ...
        <div className="row mt-3">
          {Array.isArray(customChildrenAfter)
            ? customChildrenAfter.map((Child, index) => <Child key={index} />)
            : customChildrenAfter}
        </div>
      </div>
    );
  }
}

decoratePlugin

decoratePlugin is what you export from a plugin when you want to decorate another plugin. All it is is an object with a key-value pair where the key should map exactly to the name of the plugin you are targeting. The value is a Higher Order Component that looks just like the other decorate component exports.

const myWidget = (MyView, { React, PropTypes }) =>
  class extends React.PureComponent {
    constructor(props) {
      super(props);
    }

    static displayName() {
      return 'my widget';
    }

    static getPropTypes() {
      return {
        customChildrenBefore: PropTypes.node,
        customChildren: PropTypes.node
      }
    }

    // just for fun, let's do React without JSX
    render() {
      const customChildrenBefore = React.createElement(
        'div', // element type
        { className: 'before-text' }, // props
        'My custom before text', // remaining arguments are children of new component
        this.props.customChildrenBefore // make sure to pass existing children otherwise you're plugin will overwrite other widgets.
      );
      const customChildren = React.createElement(
        'div',
        {},
        'My custom widget text',
        this.props.customChildren
      );
      return React.createElement(
        MyView,
        { customChildrenBefore, customChildren }
      );
    }
  }

// now let's expose the extension
export const decoratePlugin = { sample: myWidget };

Access to the Store

The good news is that since you are strictly just decorating an existing component, all other extensions in the API work like normal. You can use mapComponentState to pass properties from the state to Panel, and getRouteProps to pass those props from Panel to the plugin you are decorating!

export const mapComponentState = {
  Panel: (state, map) =>
    Object.assign(map, {
      network: state.node.node.network
    })
};


export const getRouteProps = {
  sample: (parentProps, props) =>
    Object.assign(props, {
      network: parentProps.network
    })
};

// now `this.props.network` will be avaialable
// in your custom widget component
← SocketsBundling Plugins →
bPanel
Docs
Quick StartPlugin OverviewTheming Overview
Community
Stack OverflowProject ChatTwitter
More
BlogGitHubStar
Copyright © 2019 bcoin
Icons made by artists from https://www.flaticon.com/authors and licensed under CC 3.0