logo

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:

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:

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:

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

<PrivateRoute />

constant from src/router/Router

Example:

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 */
    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:

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<typeof styles> {
    theme?: Theme;
}

type Props = StyleProps & RouterProps;

class IEOHeaderActionsComponent extends React.Component<Props> {
    public getAddPageIcon = () => (
        <SvgIcon width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
            <path
                d="M10 18C5.59 18 2 14.41 2 10C2 5.59 5.59 2 10
                2C14.41 2 18 5.59 18 10C18 14.41 14.41 18 10 18ZM10 0C8.68678
                0 7.38642 0.258658 6.17317 0.761205C4.95991 1.26375 3.85752 2.00035
                2.92893 2.92893C1.05357 4.8043 0 7.34784 0 10C0 12.6522 1.05357 15.1957
                2.92893 17.0711C3.85752 17.9997 4.95991 18.7362 6.17317 19.2388C7.38642
                19.7413 8.68678 20 10 20C12.6522 20 15.1957 18.9464 17.0711 17.0711C18.9464
                15.1957 20 12.6522 20 10C20 8.68678 19.7413 7.38642 19.2388 6.17317C18.7362
                4.95991 17.9997 3.85752 17.0711 2.92893C16.1425 2.00035 15.0401 1.26375 13.8268
                0.761205C12.6136 0.258658 11.3132 0 10 0ZM11 5H9V9H5V11H9V15H11V11H15V9H11V5Z"
                fill="white"
            />
        </SvgIcon>
    );

    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) && (
                <div className={classes.item} onClick={() => this.handleAddIEO()}>
                    {this.getAddPageIcon()} <span className={classes.itemText}>Add IEO</span>
                </div>
            )
        );
    }
}

export const IEOHeaderActions = compose(
    withRouter,
    withStyles(styles, { withTheme: true }),
)(IEOHeaderActionsComponent) as React.ComponentClass;

Let's put it all together to header action constant:

export const ieoActions: HeaderActions = {
    pagesWithFilter,
    pagesWithRefresh,
    customHeaderActions: <IEOHeaderActions key="ieo"/>,
};

To display plugin's page inside tower Side Bar create an array of MenuItems

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:

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:

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

[
  {
    "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

    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!