Developing a tower plugin allows you to use custom Tower with your stack
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 */
}
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
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
Root plugin's reducer has the same structure as root redux state
Example:
export const ieoPluginReducer = combineReducers({
ieoPlugin: ieoReducer,
currencies: currenciesReducer,
});
Root plugin's saga is generator function which consist of all plugin's sagas
Example:
export function* rootIEOPluginsSaga() {
yield all([
call(rootIEOSaga),
call(rootCurrenciesSaga),
]);
}
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.
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
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;
}
};
index.ts
file of your plugin's root folder exports all component, containers, modules and your plugin Object of class TowerPlugin
Edit plugins.json
file in root Tower directory
[
{
"name": "ieo",
"repo": "https://github.com/openware/tower-plugins.git",
"branch": "ieo"
}
]
src/plugins/
directory.Put plugin to env.js
file
plugins: [
{
name: 'ieo',
}
],
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!