2020-09-15 15:01:07 +02:00
|
|
|
import * as React from 'react';
|
2020-11-13 19:09:28 +02:00
|
|
|
import { useRef, useState, useEffect } from 'react';
|
|
|
|
import useWindowResizeEvent from './utils/useWindowResizeEvent';
|
|
|
|
import setLayoutItemProps from './utils/setLayoutItemProps';
|
2021-05-22 19:30:11 +02:00
|
|
|
import useLayoutItemSizes, { LayoutItemSizes, itemSize, calculateMaxSizeAvailableForItem, itemMinWidth, itemMinHeight } from './utils/useLayoutItemSizes';
|
2020-11-13 19:09:28 +02:00
|
|
|
import validateLayout from './utils/validateLayout';
|
|
|
|
import { Size, LayoutItem } from './utils/types';
|
|
|
|
import { canMove, MoveDirection } from './utils/movements';
|
|
|
|
import MoveButtons, { MoveButtonClickEvent } from './MoveButtons';
|
|
|
|
import { StyledWrapperRoot, StyledMoveOverlay, MoveModeRootWrapper, MoveModeRootMessage } from './utils/style';
|
|
|
|
import { Resizable } from 're-resizable';
|
2020-09-15 15:01:07 +02:00
|
|
|
const EventEmitter = require('events');
|
|
|
|
|
|
|
|
interface onResizeEvent {
|
2020-11-12 21:29:22 +02:00
|
|
|
layout: LayoutItem;
|
2020-09-15 15:01:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
interface Props {
|
2020-11-12 21:29:22 +02:00
|
|
|
layout: LayoutItem;
|
2020-11-12 21:13:28 +02:00
|
|
|
onResize(event: onResizeEvent): void;
|
2020-11-12 21:29:22 +02:00
|
|
|
width?: number;
|
|
|
|
height?: number;
|
|
|
|
renderItem: Function;
|
2020-11-13 19:09:28 +02:00
|
|
|
onMoveButtonClick(event: MoveButtonClickEvent): void;
|
|
|
|
moveMode: boolean;
|
|
|
|
moveModeMessage: string;
|
2020-10-09 19:35:46 +02:00
|
|
|
}
|
|
|
|
|
2020-11-13 19:09:28 +02:00
|
|
|
function itemVisible(item: LayoutItem, moveMode: boolean) {
|
|
|
|
if (moveMode) return true;
|
|
|
|
if (item.children && !item.children.length) return false;
|
|
|
|
return item.visible !== false;
|
2020-09-15 15:01:07 +02:00
|
|
|
}
|
|
|
|
|
2021-05-22 19:30:11 +02:00
|
|
|
function renderContainer(item: LayoutItem, parent: LayoutItem | null, sizes: LayoutItemSizes, resizedItemMaxSize: Size | null, onResizeStart: Function, onResize: Function, onResizeStop: Function, children: any[], isLastChild: boolean, moveMode: boolean): any {
|
2020-11-12 21:13:28 +02:00
|
|
|
const style: any = {
|
2020-11-13 19:09:28 +02:00
|
|
|
display: itemVisible(item, moveMode) ? 'flex' : 'none',
|
2020-09-15 15:01:07 +02:00
|
|
|
flexDirection: item.direction,
|
|
|
|
};
|
|
|
|
|
2020-11-13 19:09:28 +02:00
|
|
|
const size: Size = itemSize(item, parent, sizes, true);
|
2020-09-15 15:01:07 +02:00
|
|
|
|
|
|
|
const className = `resizableLayoutItem rli-${item.key}`;
|
2020-10-09 19:35:46 +02:00
|
|
|
if (item.resizableRight || item.resizableBottom) {
|
|
|
|
const enable = {
|
|
|
|
top: false,
|
|
|
|
right: !!item.resizableRight && !isLastChild,
|
|
|
|
bottom: !!item.resizableBottom && !isLastChild,
|
|
|
|
left: false,
|
|
|
|
topRight: false,
|
|
|
|
bottomRight: false,
|
|
|
|
bottomLeft: false,
|
|
|
|
topLeft: false,
|
|
|
|
};
|
|
|
|
|
2020-09-15 15:01:07 +02:00
|
|
|
return (
|
|
|
|
<Resizable
|
|
|
|
key={item.key}
|
|
|
|
className={className}
|
|
|
|
style={style}
|
|
|
|
size={size}
|
2020-11-13 19:09:28 +02:00
|
|
|
onResizeStart={onResizeStart as any}
|
|
|
|
onResize={onResize as any}
|
|
|
|
onResizeStop={onResizeStop as any}
|
2020-09-15 15:01:07 +02:00
|
|
|
enable={enable}
|
2020-11-13 19:09:28 +02:00
|
|
|
minWidth={'minWidth' in item ? item.minWidth : itemMinWidth}
|
|
|
|
minHeight={'minHeight' in item ? item.minHeight : itemMinHeight}
|
2021-05-22 19:30:11 +02:00
|
|
|
maxWidth={resizedItemMaxSize?.width}
|
|
|
|
maxHeight={resizedItemMaxSize?.height}
|
2020-09-15 15:01:07 +02:00
|
|
|
>
|
|
|
|
{children}
|
|
|
|
</Resizable>
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
return (
|
|
|
|
<div key={item.key} className={className} style={{ ...style, ...size }}>
|
|
|
|
{children}
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-12 21:13:28 +02:00
|
|
|
function ResizableLayout(props: Props) {
|
2020-09-15 15:01:07 +02:00
|
|
|
const eventEmitter = useRef(new EventEmitter());
|
|
|
|
|
|
|
|
const [resizedItem, setResizedItem] = useState<any>(null);
|
|
|
|
|
2020-11-13 19:09:28 +02:00
|
|
|
function renderItemWrapper(comp: any, item: LayoutItem, parent: LayoutItem | null, size: Size, moveMode: boolean) {
|
|
|
|
const moveOverlay = moveMode ? (
|
|
|
|
<StyledMoveOverlay>
|
|
|
|
<MoveButtons
|
|
|
|
itemKey={item.key}
|
|
|
|
onClick={props.onMoveButtonClick}
|
|
|
|
canMoveLeft={canMove(MoveDirection.Left, item, parent)}
|
|
|
|
canMoveRight={canMove(MoveDirection.Right, item, parent)}
|
|
|
|
canMoveUp={canMove(MoveDirection.Up, item, parent)}
|
|
|
|
canMoveDown={canMove(MoveDirection.Down, item, parent)}
|
|
|
|
/>
|
|
|
|
</StyledMoveOverlay>
|
|
|
|
) : null;
|
2020-09-15 15:01:07 +02:00
|
|
|
|
2020-11-13 19:09:28 +02:00
|
|
|
return (
|
|
|
|
<StyledWrapperRoot key={item.key} size={size}>
|
|
|
|
{moveOverlay}
|
|
|
|
{comp}
|
|
|
|
</StyledWrapperRoot>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
function renderLayoutItem(item: LayoutItem, parent: LayoutItem | null, sizes: LayoutItemSizes, isVisible: boolean, isLastChild: boolean): any {
|
2020-09-15 15:01:07 +02:00
|
|
|
function onResizeStart() {
|
|
|
|
setResizedItem({
|
|
|
|
key: item.key,
|
|
|
|
initialWidth: sizes[item.key].width,
|
|
|
|
initialHeight: sizes[item.key].height,
|
2021-05-22 19:30:11 +02:00
|
|
|
maxSize: calculateMaxSizeAvailableForItem(item, parent, sizes),
|
2020-09-15 15:01:07 +02:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2020-11-13 19:09:28 +02:00
|
|
|
function onResize(_event: any, direction: string, _refToElement: any, delta: any) {
|
|
|
|
const newWidth = Math.max(itemMinWidth, resizedItem.initialWidth + delta.width);
|
|
|
|
const newHeight = Math.max(itemMinHeight, resizedItem.initialHeight + delta.height);
|
|
|
|
|
|
|
|
const newSize: any = {};
|
|
|
|
|
|
|
|
if (item.width) newSize.width = item.width;
|
|
|
|
if (item.height) newSize.height = item.height;
|
|
|
|
|
|
|
|
if (direction === 'bottom') {
|
|
|
|
newSize.height = newHeight;
|
|
|
|
} else {
|
|
|
|
newSize.width = newWidth;
|
|
|
|
}
|
|
|
|
|
|
|
|
const newLayout = setLayoutItemProps(props.layout, item.key, newSize);
|
2020-09-15 15:01:07 +02:00
|
|
|
|
|
|
|
props.onResize({ layout: newLayout });
|
|
|
|
eventEmitter.current.emit('resize');
|
|
|
|
}
|
|
|
|
|
2020-11-13 19:09:28 +02:00
|
|
|
function onResizeStop(_event: any, _direction: any, _refToElement: any, delta: any) {
|
2020-09-15 15:01:07 +02:00
|
|
|
onResize(_event, _direction, _refToElement, delta);
|
|
|
|
setResizedItem(null);
|
|
|
|
}
|
|
|
|
|
2021-05-22 19:30:11 +02:00
|
|
|
const resizedItemMaxSize = item.key === resizedItem?.key ? resizedItem.maxSize : null;
|
2020-09-15 15:01:07 +02:00
|
|
|
if (!item.children) {
|
2020-11-13 19:09:28 +02:00
|
|
|
const size = itemSize(item, parent, sizes, false);
|
|
|
|
|
2020-09-15 15:01:07 +02:00
|
|
|
const comp = props.renderItem(item.key, {
|
|
|
|
item: item,
|
|
|
|
eventEmitter: eventEmitter.current,
|
2020-11-13 19:09:28 +02:00
|
|
|
size: size,
|
2020-09-15 15:01:07 +02:00
|
|
|
visible: isVisible,
|
|
|
|
});
|
|
|
|
|
2020-11-13 19:09:28 +02:00
|
|
|
const wrapper = renderItemWrapper(comp, item, parent, size, props.moveMode);
|
|
|
|
|
2021-05-22 19:30:11 +02:00
|
|
|
return renderContainer(item, parent, sizes, resizedItemMaxSize, onResizeStart, onResize, onResizeStop, [wrapper], isLastChild, props.moveMode);
|
2020-09-15 15:01:07 +02:00
|
|
|
} else {
|
|
|
|
const childrenComponents = [];
|
2020-10-09 19:35:46 +02:00
|
|
|
for (let i = 0; i < item.children.length; i++) {
|
|
|
|
const child = item.children[i];
|
2020-11-13 19:09:28 +02:00
|
|
|
childrenComponents.push(renderLayoutItem(child, item, sizes, isVisible && itemVisible(child, props.moveMode), i === item.children.length - 1));
|
2020-09-15 15:01:07 +02:00
|
|
|
}
|
|
|
|
|
2021-05-22 19:30:11 +02:00
|
|
|
return renderContainer(item, parent, sizes, resizedItemMaxSize, onResizeStart, onResize, onResizeStop, childrenComponents, isLastChild, props.moveMode);
|
2020-09-15 15:01:07 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-13 19:09:28 +02:00
|
|
|
useEffect(() => {
|
|
|
|
validateLayout(props.layout);
|
|
|
|
}, [props.layout]);
|
|
|
|
|
2020-09-15 15:01:07 +02:00
|
|
|
useWindowResizeEvent(eventEmitter);
|
2020-11-13 19:09:28 +02:00
|
|
|
const sizes = useLayoutItemSizes(props.layout, props.moveMode);
|
2020-09-15 15:01:07 +02:00
|
|
|
|
2020-11-13 19:09:28 +02:00
|
|
|
function renderMoveModeBox(rootComp: any) {
|
|
|
|
return (
|
|
|
|
<MoveModeRootWrapper>
|
|
|
|
<MoveModeRootMessage>{props.moveModeMessage}</MoveModeRootMessage>
|
|
|
|
{rootComp}
|
|
|
|
</MoveModeRootWrapper>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
const rootComp = renderLayoutItem(props.layout, null, sizes, itemVisible(props.layout, props.moveMode), true);
|
|
|
|
|
|
|
|
if (props.moveMode) {
|
|
|
|
return renderMoveModeBox(rootComp);
|
|
|
|
} else {
|
|
|
|
return rootComp;
|
|
|
|
}
|
2020-09-15 15:01:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
export default ResizableLayout;
|