Tower Plugin

Developing a tower plugin allows you to use custom Tower with your stack

Tower Plugin Interface

Plugin should be an object of class TowerPlugin

export interface TowerPluginInterface {
    getReduxReducer: () => Reducer<CombinedState<{}>>;              /* root plugin's redux reducer */
    // tslint:disable-next-line: no-any
    getReduxSaga: () => any;                                        /* root plugin's redux saga */
    getRoutes: (userLoading, isCurrentSession) => JSX.Element[];    /* plugin's routes */
    getHeaderActions: () => HeaderActions;                          /* plugin's header actions like Filter, Refresh at the NavBar */
    getMenu: () => MenuItem[];                                      /* plugin's menu items */
    getMenuIcons: (name: string) => JSX.Element;                    /* plugin's menu icons */
}

Starting

Create a folder with your plugin's name inside tower/src/plugins

Folder should contain root index.ts file with plugin object.

Example:

export * from './containers';
export * from './components';
export * from './modules';

export const IeoPlugin: TowerPluginInterface =
    new TowerPlugin(ieoPluginReducer, rootIEOPluginsSaga, ieoRoutes, ieoActions, ieoMenuItem, ieoMenuIcons);

Each prop of TowerPlugin constructor will be described further

Redux State

In case your plugin can have several redux modules you should export root plugin's state from modules

Example: typescript export interface IeoPluginState { ieoPlugin: StateIEO; currencies: CurrenciesState; } ** State's name should have structure ${PluginNameInCamelCase}PluginState

Redux Reducer

Root plugin's reducer has the same structure as root redux state

Example: typescript export const ieoPluginReducer = combineReducers({ ieoPlugin: ieoReducer, currencies: currenciesReducer, });

Redux Saga

Root plugin's saga is generator function which consist of all plugin's sagas

Example: typescript export function* rootIEOPluginsSaga() { yield all([ call(rootIEOSaga), call(rootCurrenciesSaga), ]); }

Routes

As described earlier routes prop of TowerPlugin constructor is a constant which returns an array of JSX elements.

Here you define all routes you need for your plugin. If a route requires admin to be logged in to Tower use typescript <PrivateRoute /> constant from src/router/Router

Example: jsx export const ieoRoutes = (userLoading, isCurrentSession) => { return ([ <PrivateRoute loading={userLoading} isLogged={isCurrentSession} exact={true} path="/tower/plugins/ieo/:id/edit" component={IEOTabs} />, <PrivateRoute loading={userLoading} isLogged={isCurrentSession} exact={true} path="/tower/plugins/ieo" component={IEO} />, ]); }; Props userLoading, isCurrentSession will be transferred automatically to the function, don't worry about them.

  • UserLoading will help your component to render Loading screen if the data about user hasn't loaded yet from the backend.

    • isCurrentSession shows to the component that user is logged in.

Header Actions

For the case when your page needs ability to open Filter component, manual Refresh of the page or Export action create a constant of type HeaderActions:

export interface HeaderActions {
    pagesWithFilter?: string[];                     /* routes which requires filter icon */
    pagesWithRefresh?: string[];                    /* routes which requires refresh icon */
    pagesWithImport?: string[];                     /* routes which requires import icon */
    pagesWithExport?: string[];                     /* routes which requires export icon */
    customHeaderActions?: JSX.Element;              /* custom header actions */
}

Example

export const pagesWithFilter = ['/tower/plugins/ieo'];
export const pagesWithRefresh = ['/tower/plugins/ieo'];
export const pagesWithExport = ['/tower/plugins/ieo'];

Custom header actions is using for cases when you need to open a modal by clicking on Tower NavBar, redirect to Add IEO pages etc.

customHeaderActions is a JSX Element with * an icon which you want to show near header action * an action itself (opening a modal, redirect to a page) * a route (page where you want to see that action in NavBar).

Example: ```tsx import { createStyles, SvgIcon, Theme, WithStyles } from '@material-ui/core'; import { withStyles } from '@material-ui/core/styles'; import { History } from 'history'; import * as React from 'react'; import { withRouter } from 'react-router-dom'; import { compose } from 'redux';

const styles = () => createStyles({ item: { display: 'flex', alignItems: 'center', marginLeft: 4, cursor: 'pointer', transition: 'background-color 150ms cubic-bezier(0.4, 0, 0.2, 1) 0ms', padding: '5px 10px', borderRadius: 5, '&:hover': { backgroundColor: '#ffffff1a', }, }, itemText: { textTransform: 'uppercase', paddingLeft: 6, fontWeight: 'bold', fontSize: 12, letterSpacing: 0.4, }, });

interface RouterProps { history: History; }

interface StyleProps extends WithStyles { theme?: Theme; }

type Props = StyleProps & RouterProps;

class IEOHeaderActionsComponent extends React.Component { public getAddPageIcon = () => ( );

public shouldShowAddIEO(location: string): boolean { return ['/tower/plugins/ieo'].includes(location); }

public handleAddIEO() { this.props.history.push('/tower/plugins/ieo/add'); }

public render() { const { history, classes } = this.props; return ( this.shouldShowAddIEO(history.location.pathname) && (

this.handleAddIEO()}> {this.getAddPageIcon()} Add IEO
) ); } }

export const IEOHeaderActions = compose( withRouter, withStyles(styles, { withTheme: true }), )(IEOHeaderActionsComponent) as React.ComponentClass; Let's put it all together to header action constant: typescript export const ieoActions: HeaderActions = { pagesWithFilter, pagesWithRefresh, customHeaderActions: , }; ```

To display plugin's page inside tower Side Bar create an array of MenuItems typescript export interface MenuItem { key: string; /* a route to a page */ value: string; /* menu item's name */ isLink: boolean; /* if true means it opens the page by click, */ } /* false - opens nested sidebar */ Example: typescript export const ieoMenuItem: MenuItem[] = [ { key: '/tower/plugins/ieo', value: 'IEO', isLink: true }, ];

If you want to express the meaning of menu item's by some nice icon create a constant which returns a specific icon by route.

Example: typescript export const ieoMenuIcons = (name: string) => { switch (name) { case '/tower/plugins/ieo': return ( <svg width="32" height="19" viewBox="0 0 24 19" fill="none" xmlns="http://www.w3.org/2000/svg"> <path fillRule="evenodd" clipRule="evenodd" d="..." fill="#979797"/> </svg> ); default: return; } };

Next Steps

  • Check that index.ts file of your plugin's root folder exports all component, containers, modules and your plugin Object of class TowerPlugin
  • Push your plugin folder to a separate github repo
  • Now you can return to basic Tower stable version
  • In order to build custom image you need to set configs to tower ## Configure tower to use custom plugins Edit plugins.json file in root Tower directory json [ { "name": "ieo", "repo": "https://github.com/openware/tower-plugins.git", "branch": "ieo" } ]
    1. Define plugins name (same as the folder which you developed previously inside src/plugins/ directory.
    2. Put a link to github repository which contains plugins files
    3. Define github repository branch

Put plugin to env.js file javascript plugins: [ { name: 'ieo', } ],

Building an image

yarn build

This command will clone the github repository you specified in plugins.json file, generate TowerTemplate file and connect your plugin to basic tower components!