607 lines
21 KiB
TypeScript
607 lines
21 KiB
TypeScript
|
/**
|
||
|
* @author Martin Karkowski
|
||
|
* @email m.karkowski@zema.de
|
||
|
* @create date 2019-02-20 09:19:06
|
||
|
* @modify date 2020-10-29 09:55:30
|
||
|
* @desc [description]
|
||
|
*
|
||
|
* A Basic DynamicLayout.
|
||
|
* I uses a Toolbar, Sidebar
|
||
|
*
|
||
|
* Toast are implemented by https://fkhadra.github.io/react-toastify/introduction
|
||
|
* Dynamic Account https://github.com/STRML/react-grid-layout
|
||
|
*
|
||
|
*/
|
||
|
|
||
|
import React from 'react';
|
||
|
import { Card, Modal } from 'react-bootstrap';
|
||
|
import { toast, ToastContainer } from 'react-toastify';
|
||
|
import 'react-toastify/dist/ReactToastify.css';
|
||
|
import { rgetattr, rsetattr } from '../../../lib/helpers/objectMethods';
|
||
|
import DynamicRenderer from '../dynamic/dynamicRenderer';
|
||
|
import { IHotKeyAction } from './interfaces/IHotkeyAction';
|
||
|
import { IModalSettings } from './interfaces/IModalSettings';
|
||
|
import Toolbar, { IMenu, ToolbarProps } from './toolbar';
|
||
|
import { IDynamicRenderSettings } from '../dynamic/interfaces/IDynamicRenderSettings'
|
||
|
import { generateId } from '../../../lib/helpers/idMethods';
|
||
|
import { faEye, faEyeSlash, faMinusSquare, faWindowClose } from '@fortawesome/free-solid-svg-icons';
|
||
|
import GridLayout, { Responsive, WidthProvider } from 'react-grid-layout';
|
||
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||
|
|
||
|
export interface IDynamicWidget extends IDynamicRenderSettings {
|
||
|
id: string,
|
||
|
label: string,
|
||
|
visible: boolean,
|
||
|
gridSettings: {
|
||
|
|
||
|
// A string corresponding to the component key
|
||
|
i?: string,
|
||
|
|
||
|
// These are all in grid units, not pixels
|
||
|
x: number,
|
||
|
y: number,
|
||
|
w: number,
|
||
|
h: number,
|
||
|
minW?: number;
|
||
|
maxW?: number;
|
||
|
minH?: number;
|
||
|
maxH?: number;
|
||
|
|
||
|
// If true, equal to `isDraggable: false, isResizable: false`.
|
||
|
static?: boolean,
|
||
|
// If false, will not be draggable. Overrides `static`.
|
||
|
isDraggable?: boolean;
|
||
|
// If false, will not be resizable. Overrides `static`.
|
||
|
isResizable?: boolean;
|
||
|
// By default, a handle is only shown on the bottom-right (southeast) corner.
|
||
|
// Note that resizing from the top or left is generally not intuitive.
|
||
|
// Defaults to ['se']
|
||
|
resizeHandles?: Array<'s' | 'w' | 'e' | 'n' | 'sw' | 'nw' | 'se' | 'ne'>
|
||
|
// If true and draggable, item will be moved only within grid.
|
||
|
isBounded?: boolean
|
||
|
},
|
||
|
showLabel?:boolean;
|
||
|
hideable?: boolean;
|
||
|
bg?: 'primary' | 'secondary' | 'success' | 'danger' | 'warning' | 'info' | 'dark' | 'light';
|
||
|
border?: 'primary' | 'secondary' | 'success' | 'danger' | 'warning' | 'info' | 'dark' | 'light';
|
||
|
text?:'primary' | 'secondary' | 'success' | 'danger' | 'warning' | 'info' | 'dark' | 'light';
|
||
|
}
|
||
|
|
||
|
// All callbacks below have signature (layout, oldItem, newItem, placeholder, e, element).
|
||
|
// 'start' and 'stop' callbacks pass `undefined` for 'placeholder'.
|
||
|
export type ItemCallback = (layout, oldItem, newItem, placeholder, e: MouseEvent, element: HTMLElement) => void;
|
||
|
|
||
|
export interface IDynamicLayoutProps<CallbackData> extends ToolbarProps<CallbackData> {
|
||
|
hotkeys?: IHotKeyAction<CallbackData>[];
|
||
|
components: IDynamicWidget[]
|
||
|
layoutSettings: {
|
||
|
//
|
||
|
// Basic props
|
||
|
//
|
||
|
|
||
|
// This allows setting the initial width on the server side.
|
||
|
// This is required unless using the HOC <WidthProvider> or similar
|
||
|
width?: number,
|
||
|
|
||
|
// If true, the container height swells and contracts to fit contents
|
||
|
autoSize?: boolean,
|
||
|
|
||
|
// Number of columns in this layout.
|
||
|
cols?: number,
|
||
|
|
||
|
// A CSS selector for tags that will not be draggable.
|
||
|
// For example: draggableCancel:'.MyNonDraggableAreaClassName'
|
||
|
// If you forget the leading . it will not work.
|
||
|
draggableCancel?: string,
|
||
|
|
||
|
// A CSS selector for tags that will act as the draggable handle.
|
||
|
// For example: draggableHandle:'.MyDragHandleClassName'
|
||
|
// If you forget the leading . it will not work.
|
||
|
draggableHandle?: string,
|
||
|
|
||
|
// If true, the layout will compact vertically
|
||
|
verticalCompact?: boolean,
|
||
|
|
||
|
// Compaction type.
|
||
|
compactType?: ('vertical' | 'horizontal');
|
||
|
|
||
|
// Layout is an array of object with the format:
|
||
|
// {x: number, y: number, w: number, h: number}
|
||
|
// The index into the layout must match the key used on each item component.
|
||
|
// If you choose to use custom keys, you can specify that key in the layout
|
||
|
// array objects like so:
|
||
|
// {i: string, x: number, y: number, w: number, h: number}
|
||
|
layout?: Array<[number, number, number, number]> | Array<{ i: string, x: number, y: number, w: number, h: number }> // If not provided, use data-grid props on children
|
||
|
|
||
|
// Margin between items [x, y] in px.
|
||
|
margin?: [number, number],
|
||
|
|
||
|
// Padding inside the container [x, y] in px
|
||
|
containerPadding?: [number, number]
|
||
|
|
||
|
// Rows have a static height, but you can change this based on breakpoints
|
||
|
// if you like.
|
||
|
rowHeight?: number
|
||
|
|
||
|
//
|
||
|
// Flags
|
||
|
//
|
||
|
isDraggable?: boolean,
|
||
|
isResizable?: boolean,
|
||
|
isBounded?: boolean,
|
||
|
// Uses CSS3 translate() instead of position top/left.
|
||
|
// This makes about 6x faster paint performance
|
||
|
useCSSTransforms?: boolean,
|
||
|
// If parent DOM node of ResponsiveReactGridLayout or ReactGridLayout has "transform: scale(n)" css property,
|
||
|
// we should set scale coefficient to avoid render artefacts while dragging.
|
||
|
transformScale?: number,
|
||
|
|
||
|
// If true, grid items won't change position when being
|
||
|
// dragged over.
|
||
|
preventCollision?: boolean;
|
||
|
|
||
|
// If true, droppable elements (with `draggable={true}` attribute)
|
||
|
// can be dropped on the grid. It triggers "onDrop" callback
|
||
|
// with position and event object as parameters.
|
||
|
// It can be useful for dropping an element in a specific position
|
||
|
//
|
||
|
// NOTE: In case of using Firefox you should add
|
||
|
// `onDragStart={e => e.dataTransfer.setData('text/plain', '')}` attribute
|
||
|
// along with `draggable={true}` otherwise this feature will work incorrect.
|
||
|
// onDragStart attribute is required for Firefox for a dragging initialization
|
||
|
// @see https://bugzilla.mozilla.org/show_bug.cgi?id=568313
|
||
|
isDroppable?: boolean
|
||
|
// Defines which resize handles should be rendered
|
||
|
// Allows for any combination of:
|
||
|
// 's' - South handle (bottom-center)
|
||
|
// 'w' - West handle (left-center)
|
||
|
// 'e' - East handle (right-center)
|
||
|
// 'n' - North handle (top-center)
|
||
|
// 'sw' - Southwest handle (bottom-left)
|
||
|
// 'nw' - Northwest handle (top-left)
|
||
|
// 'se' - Southeast handle (bottom-right)
|
||
|
// 'ne' - Northeast handle (top-right)
|
||
|
resizeHandles?: Array<'s' | 'w' | 'e' | 'n' | 'sw' | 'nw' | 'se' | 'ne'>
|
||
|
// Custom component for resize handles
|
||
|
resizeHandle?: React.ReactElement<any> | ((resizeHandleAxis) => React.ReactElement<any>)
|
||
|
|
||
|
//
|
||
|
// Callbacks
|
||
|
//
|
||
|
|
||
|
// Callback so you can save the layout.
|
||
|
// Calls back with (currentLayout) after every drag or resize stop.
|
||
|
onLayoutChange?: (layout: GridLayout) => void,
|
||
|
|
||
|
// Calls when drag starts.
|
||
|
onDragStart?: ItemCallback,
|
||
|
// Calls on each drag movement.
|
||
|
onDrag?: ItemCallback,
|
||
|
// Calls when drag is complete.
|
||
|
onDragStop?: ItemCallback,
|
||
|
// Calls when resize starts.
|
||
|
onResizeStart?: ItemCallback,
|
||
|
// Calls when resize movement happens.
|
||
|
onResize?: ItemCallback,
|
||
|
// Calls when resize is complete.
|
||
|
onResizeStop?: ItemCallback,
|
||
|
// Calls when an element has been dropped into the grid from outside.
|
||
|
onDrop?: (layout, item, e: Event) => void
|
||
|
|
||
|
// Ref for getting a reference for the grid's wrapping div.
|
||
|
// You can use this instead of a regular ref and the deprecated `ReactDOM.findDOMNode()`` function.
|
||
|
innerRef?: React.Ref<"div">
|
||
|
},
|
||
|
onResize?: () => void;
|
||
|
onMount?: (layout: DynamicLayout<CallbackData>) => void;
|
||
|
onUnmount?: () => void;
|
||
|
onWidgetToggle?: (widget: IDynamicWidget, visble: boolean) => Promise<boolean>;
|
||
|
menuName?: string
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
export interface IDynamicLayoutState<CallbackData> extends IDynamicLayoutProps<CallbackData> {
|
||
|
modalSettings?: IModalSettings
|
||
|
modalVisible?: boolean
|
||
|
}
|
||
|
|
||
|
class DynamicLayout<CallbackData> extends React.Component<IDynamicLayoutProps<CallbackData>, IDynamicLayoutState<CallbackData>> {
|
||
|
|
||
|
protected _handleResize: () => void;
|
||
|
protected _handleKeyDown: (event) => void;
|
||
|
protected _handleKeyUp: (event) => void;
|
||
|
protected _handleMouse: (event: MouseEvent) => void;
|
||
|
|
||
|
|
||
|
protected _menuEntryId: string;
|
||
|
|
||
|
public hotkeysEnabled = true;
|
||
|
public pressedKey: string = null;
|
||
|
public currentMousePosition: MouseEvent;
|
||
|
|
||
|
/**
|
||
|
* Function will be called if the Item has been rendered sucessfully.
|
||
|
*/
|
||
|
componentDidMount() {
|
||
|
|
||
|
const _this = this;
|
||
|
|
||
|
// Listening to resizing Events
|
||
|
function handleResize() {
|
||
|
if (typeof _this.state.onResize === 'function') {
|
||
|
_this.state.onResize();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function handleKeyDown(event) {
|
||
|
|
||
|
if (!_this.hotkeysEnabled || _this.state.hotkeys?.length === 0) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (_this.pressedKey !== event.code) {
|
||
|
_this.pressedKey = event.code;
|
||
|
|
||
|
try {
|
||
|
for (const hotkey of _this.state.hotkeys || []) {
|
||
|
if (hotkey.key === event.code) {
|
||
|
console.log('perform', hotkey.key)
|
||
|
hotkey.onPress(_this.state.generateData(), event)
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
} catch (e) {
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function handleKeyUp(event) {
|
||
|
if (!_this.hotkeysEnabled || _this.state.hotkeys?.length === 0) {
|
||
|
return;
|
||
|
}
|
||
|
_this.pressedKey='';
|
||
|
try {
|
||
|
for (const hotkey of _this.state.hotkeys || []) {
|
||
|
if (hotkey.key === event.code && hotkey.onRelease) {
|
||
|
hotkey.onRelease(_this.state.generateData(), event)
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
} catch (e) {
|
||
|
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function handleMouse(event) {
|
||
|
_this.currentMousePosition = event;
|
||
|
}
|
||
|
|
||
|
if (typeof _this.state.onMount === 'function') {
|
||
|
_this.state.onMount(this);
|
||
|
}
|
||
|
|
||
|
this._handleResize = handleResize;
|
||
|
this._handleKeyDown = handleKeyDown;
|
||
|
this._handleKeyUp = handleKeyUp;
|
||
|
this._handleMouse = handleMouse;
|
||
|
window.addEventListener('resize', this._handleResize);
|
||
|
window.addEventListener('keydown', this._handleKeyDown);
|
||
|
window.addEventListener('keyup', this._handleKeyUp);
|
||
|
window.addEventListener('mousemove', this._handleMouse);
|
||
|
|
||
|
|
||
|
|
||
|
this.widgets = this.widgets;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Function, that will be called before the network fails.
|
||
|
*/
|
||
|
componentWillUnmount() {
|
||
|
window.removeEventListener('resize', this._handleResize);
|
||
|
window.removeEventListener('keydown', this._handleKeyDown);
|
||
|
window.removeEventListener('keyup', this._handleKeyUp);
|
||
|
window.removeEventListener('mousemove', this._handleMouse);
|
||
|
|
||
|
// Call the unmount
|
||
|
if (typeof this.state.onUnmount === 'function') {
|
||
|
this.state.onUnmount();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
constructor(props) {
|
||
|
super(props);
|
||
|
|
||
|
this.state = Object.assign({
|
||
|
modalSettings: null,
|
||
|
modalVisible: false
|
||
|
}, this.props);
|
||
|
|
||
|
// Create a Menu Entry.
|
||
|
this._menuEntryId = generateId();
|
||
|
}
|
||
|
|
||
|
public openPopup(settings: IModalSettings) {
|
||
|
const modalSettings = settings;
|
||
|
|
||
|
const _this = this;
|
||
|
|
||
|
const _close = () => {
|
||
|
_this.setState({
|
||
|
modalVisible: false
|
||
|
});
|
||
|
};
|
||
|
|
||
|
const _originalFunction = modalSettings.onHide;
|
||
|
modalSettings.onHide = (...args) => {
|
||
|
if (typeof _originalFunction === 'function') {
|
||
|
_originalFunction(_close);
|
||
|
} else {
|
||
|
_close();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
_this.setState({
|
||
|
modalVisible: true,
|
||
|
modalSettings: modalSettings
|
||
|
});
|
||
|
|
||
|
// Return the Close Method.
|
||
|
return _close;
|
||
|
}
|
||
|
|
||
|
public getDialogData<T = any>(settings: IModalSettings) {
|
||
|
const _this = this;
|
||
|
let close: () => void;
|
||
|
|
||
|
return new Promise<T>((resolve, reject) => {
|
||
|
// Adapt the Settings:
|
||
|
const _onSubmit = rgetattr<(data) => Promise<void>>(settings, 'content.props.onSubmit', async (data) => { });
|
||
|
const _onCancel = rgetattr<(err) => Promise<void>>(settings, 'content.props.onCancel', async (err) => { });
|
||
|
|
||
|
const onSubmit = async (data) => {
|
||
|
close();
|
||
|
_onSubmit(data);
|
||
|
resolve(data);
|
||
|
}
|
||
|
|
||
|
const onCancel = async (data) => {
|
||
|
close();
|
||
|
_onCancel(data);
|
||
|
reject(data);
|
||
|
}
|
||
|
|
||
|
rsetattr(settings, 'content.props.onSubmit', onSubmit);
|
||
|
rsetattr(settings, 'content.props.onCancel', onCancel);
|
||
|
|
||
|
close = _this.openPopup(settings);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
private _dynamicMenuEntry: IMenu<CallbackData> = null;
|
||
|
|
||
|
public get dynamicMenuEntry() {
|
||
|
|
||
|
if (this._dynamicMenuEntry === null) {
|
||
|
|
||
|
this._dynamicMenuEntry = {
|
||
|
type: 'menu',
|
||
|
id: this._menuEntryId,
|
||
|
label: this.props.menuName || 'Widgets',
|
||
|
items: []
|
||
|
};
|
||
|
|
||
|
this.toolbar.push(this._dynamicMenuEntry)
|
||
|
}
|
||
|
|
||
|
return this._dynamicMenuEntry;
|
||
|
|
||
|
}
|
||
|
|
||
|
public get toolbar() {
|
||
|
return this.state.toolbar.items;
|
||
|
}
|
||
|
|
||
|
public get widgets(): IDynamicWidget[] {
|
||
|
return this.state.components;
|
||
|
}
|
||
|
|
||
|
public set widgets(value: IDynamicWidget[]) {
|
||
|
|
||
|
const menu = this.dynamicMenuEntry;
|
||
|
|
||
|
const _this = this;
|
||
|
|
||
|
menu.items = value.filter(item => item.hideable).map(item => {
|
||
|
return {
|
||
|
type: 'action',
|
||
|
onClick() {
|
||
|
_this.toggleWidget(item, !item.visible);
|
||
|
},
|
||
|
label: (item.visible ? 'Hide' : 'Show') + ' Widget "' + item.label + '"',
|
||
|
icon: (item.visible ? faEyeSlash : faEye)
|
||
|
}
|
||
|
})
|
||
|
|
||
|
this.setState({
|
||
|
components: value,
|
||
|
toolbar: {
|
||
|
items: this.toolbar
|
||
|
}
|
||
|
})
|
||
|
}
|
||
|
|
||
|
public async toggleWidget(widget: IDynamicWidget | string, visibility: boolean) {
|
||
|
let widgetToAdapt: IDynamicWidget;
|
||
|
|
||
|
const widgets = this.widgets;
|
||
|
|
||
|
if (typeof widget === "string") {
|
||
|
for (const _widget of widgets) {
|
||
|
if (_widget.id = widget) {
|
||
|
widgetToAdapt = _widget;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
} else {
|
||
|
widgetToAdapt = widget;
|
||
|
}
|
||
|
|
||
|
let adapted = false;
|
||
|
|
||
|
if (typeof this.state.onWidgetToggle === 'function') {
|
||
|
// Call the Async Function to select a Tab.
|
||
|
if (await this.state.onWidgetToggle(widgetToAdapt, visibility)) {
|
||
|
widgetToAdapt.visible = visibility;
|
||
|
adapted = true;
|
||
|
}
|
||
|
} else {
|
||
|
// Change the visiblity.
|
||
|
widgetToAdapt.visible = visibility;
|
||
|
adapted = true;
|
||
|
}
|
||
|
|
||
|
if (adapted) {
|
||
|
// Update the State.
|
||
|
this.widgets = widgets;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Helper Function to render a Toast.
|
||
|
* @param message The Message of the Toast
|
||
|
* @param type The Type.
|
||
|
* @param timeout A Timeout in [ms], after which the Toast should disapear. 0 = infinity
|
||
|
*/
|
||
|
public showToast(message: string, type?: 'info' | 'warning' | 'success' | 'error' | 'default' | 'dark' | 'light' | 'dark', autoClose = 5000) {
|
||
|
if (type !== undefined && type !== null) {
|
||
|
toast[type](message, {
|
||
|
position: "top-center",
|
||
|
autoClose,
|
||
|
hideProgressBar: false,
|
||
|
closeOnClick: true,
|
||
|
pauseOnHover: true,
|
||
|
draggable: true,
|
||
|
progress: undefined,
|
||
|
});
|
||
|
} else {
|
||
|
toast(message, {
|
||
|
position: "top-center",
|
||
|
autoClose,
|
||
|
hideProgressBar: false,
|
||
|
closeOnClick: true,
|
||
|
pauseOnHover: true,
|
||
|
draggable: true,
|
||
|
progress: undefined,
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public render() {
|
||
|
const _this = this;
|
||
|
|
||
|
const toolbar = (<Toolbar toolbar={this.state.toolbar} generateData={this.state.generateData} brand={{
|
||
|
label: 'nopeBackend',
|
||
|
ref: '/',
|
||
|
type: 'link',
|
||
|
}}></Toolbar>);
|
||
|
|
||
|
let layout = this.state.components.filter(item => item.visible).map(item => [item.gridSettings.x,item.gridSettings.y,item.gridSettings.w,item.gridSettings.h]);
|
||
|
layout = layout.length > 0 ? layout : undefined;
|
||
|
|
||
|
|
||
|
const children = this.state.components.filter(item => item.visible).map((item, idx) => {
|
||
|
if (item.hideable) {
|
||
|
return (
|
||
|
<Card key={idx} data-grid={item.gridSettings} bg={item.bg} border={item.border} text={item.text} >
|
||
|
<FontAwesomeIcon icon={faWindowClose} style={{
|
||
|
position: 'absolute',
|
||
|
top: '-5px',
|
||
|
left: '-5px'
|
||
|
}} onClick={_ => _this.toggleWidget(item, !item.visible)}/>
|
||
|
|
||
|
{ item.showLabel ? <Card.Header>{item.label}</Card.Header> : ''}
|
||
|
|
||
|
<Card.Body style={{overflow: 'scroll'}}>
|
||
|
{
|
||
|
React.createElement(DynamicRenderer, { ...item })
|
||
|
}
|
||
|
</Card.Body>
|
||
|
</Card>
|
||
|
)
|
||
|
}
|
||
|
|
||
|
return (
|
||
|
<Card key={idx} data-grid={item.gridSettings} bg={item.bg} border={item.border} text={item.text}>
|
||
|
{ item.showLabel ? <Card.Header>{item.label}</Card.Header> : ''}
|
||
|
<Card.Body style={{ overflow: 'scroll' }}>
|
||
|
{
|
||
|
React.createElement(DynamicRenderer, { ...item })
|
||
|
}
|
||
|
</Card.Body>
|
||
|
</Card>)
|
||
|
});
|
||
|
|
||
|
const ResponsiveGridLayout = WidthProvider(Responsive);
|
||
|
const grid = React.createElement(GridLayout, {
|
||
|
...this.state.layoutSettings,
|
||
|
layout
|
||
|
// breakpoints: {
|
||
|
// lg: 1200,
|
||
|
// md: 996,
|
||
|
// sm: 768,
|
||
|
// xs: 480,
|
||
|
// xxs: 0
|
||
|
// },
|
||
|
// cols: {
|
||
|
// lg: 12,
|
||
|
// md: 10,
|
||
|
// sm: 6,
|
||
|
// xs: 4,
|
||
|
// xxs: 1
|
||
|
// }
|
||
|
} as any, ...children);
|
||
|
|
||
|
const toast = (<ToastContainer
|
||
|
position="top-center"
|
||
|
autoClose={5000}
|
||
|
hideProgressBar={false}
|
||
|
newestOnTop={false}
|
||
|
closeOnClick
|
||
|
rtl={false}
|
||
|
pauseOnFocusLoss
|
||
|
draggable
|
||
|
pauseOnHover
|
||
|
/>);
|
||
|
|
||
|
const renderedElements = [toolbar, grid, toast];
|
||
|
if (this.state.modalVisible && this.state.modalSettings != undefined) {
|
||
|
renderedElements.push((<Modal
|
||
|
show={this.state.modalVisible}
|
||
|
backdrop={typeof this.state.modalSettings.backdrop !== undefined ? this.state.modalSettings.backdrop : 'static'}
|
||
|
keyboard={false}
|
||
|
size={this.state.modalSettings.size}
|
||
|
centered={this.state.modalSettings.centered || false}
|
||
|
onHide={this.state.modalSettings.onHide}
|
||
|
scrollable
|
||
|
>
|
||
|
{
|
||
|
this.state.modalSettings.header ?
|
||
|
<Modal.Header>
|
||
|
<Modal.Title>{this.state.modalSettings.header}</Modal.Title>
|
||
|
</Modal.Header> :
|
||
|
''
|
||
|
}
|
||
|
|
||
|
<Modal.Body>
|
||
|
{/* Render the dynamic Component */}
|
||
|
<DynamicRenderer component={this.state.modalSettings.content.component} props={this.state.modalSettings.content.props} />
|
||
|
</Modal.Body>
|
||
|
</Modal>));
|
||
|
}
|
||
|
return React.createElement("div", {}, ...renderedElements);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
export default DynamicLayout;
|