2020-09-15 15:01:07 +02:00
|
|
|
import * as React from 'react';
|
|
|
|
import { useRef, useState } from 'react';
|
|
|
|
import produce from 'immer';
|
|
|
|
import useWindowResizeEvent from './hooks/useWindowResizeEvent';
|
|
|
|
import useLayoutItemSizes, { LayoutItemSizes, itemSize } from './hooks/useLayoutItemSizes';
|
|
|
|
const { Resizable } = require('re-resizable');
|
|
|
|
const EventEmitter = require('events');
|
|
|
|
|
2020-10-09 19:35:46 +02:00
|
|
|
export const dragBarThickness = 5;
|
|
|
|
|
2020-09-15 15:01:07 +02:00
|
|
|
export enum LayoutItemDirection {
|
|
|
|
Row = 'row',
|
|
|
|
Column = 'column',
|
|
|
|
}
|
|
|
|
|
|
|
|
export interface Size {
|
|
|
|
width: number,
|
|
|
|
height: number,
|
|
|
|
}
|
|
|
|
|
|
|
|
export interface LayoutItem {
|
|
|
|
key: string,
|
|
|
|
width?: number,
|
|
|
|
height?: number,
|
|
|
|
minWidth?: number,
|
|
|
|
minHeight?: number,
|
|
|
|
children?: LayoutItem[]
|
|
|
|
direction?: LayoutItemDirection,
|
2020-10-09 19:35:46 +02:00
|
|
|
resizableRight?: boolean,
|
|
|
|
resizableBottom?: boolean,
|
2020-09-15 15:01:07 +02:00
|
|
|
visible?: boolean,
|
2020-10-09 19:35:46 +02:00
|
|
|
context?: any,
|
2020-09-15 15:01:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
interface onResizeEvent {
|
|
|
|
layout: LayoutItem
|
|
|
|
}
|
|
|
|
|
|
|
|
interface Props {
|
|
|
|
layout: LayoutItem,
|
2020-11-12 21:13:28 +02:00
|
|
|
onResize(event: onResizeEvent): void;
|
2020-09-15 15:01:07 +02:00
|
|
|
width?: number,
|
|
|
|
height?: number,
|
2020-10-09 19:35:46 +02:00
|
|
|
renderItem: Function,
|
|
|
|
}
|
|
|
|
|
2020-11-12 21:13:28 +02:00
|
|
|
export function allDynamicSizes(layout: LayoutItem): any {
|
|
|
|
const output: any = {};
|
2020-10-09 19:35:46 +02:00
|
|
|
|
2020-11-12 21:13:28 +02:00
|
|
|
function recurseProcess(item: LayoutItem) {
|
2020-10-09 19:35:46 +02:00
|
|
|
if (item.resizableBottom || item.resizableRight) {
|
|
|
|
if ('width' in item || 'height' in item) {
|
2020-11-12 21:13:28 +02:00
|
|
|
const size: any = {};
|
2020-10-09 19:35:46 +02:00
|
|
|
if ('width' in item) size.width = item.width;
|
|
|
|
if ('height' in item) size.height = item.height;
|
|
|
|
output[item.key] = size;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (item.children) {
|
|
|
|
for (const child of item.children) {
|
|
|
|
recurseProcess(child);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
recurseProcess(layout);
|
|
|
|
|
|
|
|
return output;
|
2020-09-15 15:01:07 +02:00
|
|
|
}
|
|
|
|
|
2020-11-12 21:13:28 +02:00
|
|
|
export function findItemByKey(layout: LayoutItem, key: string): LayoutItem {
|
|
|
|
function recurseFind(item: LayoutItem): LayoutItem {
|
2020-09-15 15:01:07 +02:00
|
|
|
if (item.key === key) return item;
|
|
|
|
|
|
|
|
if (item.children) {
|
|
|
|
for (const child of item.children) {
|
|
|
|
const found = recurseFind(child);
|
|
|
|
if (found) return found;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
const output = recurseFind(layout);
|
|
|
|
if (!output) throw new Error(`Invalid item key: ${key}`);
|
|
|
|
return output;
|
|
|
|
}
|
|
|
|
|
2020-11-12 21:13:28 +02:00
|
|
|
function updateLayoutItem(layout: LayoutItem, key: string, props: any) {
|
|
|
|
return produce(layout, (draftState: LayoutItem) => {
|
|
|
|
function recurseFind(item: LayoutItem) {
|
2020-09-15 15:01:07 +02:00
|
|
|
if (item.key === key) {
|
|
|
|
for (const n in props) {
|
|
|
|
(item as any)[n] = props[n];
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (item.children) {
|
|
|
|
for (const child of item.children) {
|
|
|
|
recurseFind(child);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
recurseFind(draftState);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2020-11-12 21:13:28 +02:00
|
|
|
function renderContainer(item: LayoutItem, sizes: LayoutItemSizes, onResizeStart: Function, onResize: Function, onResizeStop: Function, children: any[], isLastChild: boolean): any {
|
|
|
|
const style: any = {
|
2020-09-15 15:01:07 +02:00
|
|
|
display: item.visible !== false ? 'flex' : 'none',
|
|
|
|
flexDirection: item.direction,
|
|
|
|
};
|
|
|
|
|
2020-11-12 21:13:28 +02:00
|
|
|
const size: Size = itemSize(item, sizes);
|
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,
|
|
|
|
};
|
|
|
|
|
|
|
|
if (item.resizableRight) style.paddingRight = dragBarThickness;
|
|
|
|
if (item.resizableBottom) style.paddingBottom = dragBarThickness;
|
2020-09-15 15:01:07 +02:00
|
|
|
|
|
|
|
return (
|
|
|
|
<Resizable
|
|
|
|
key={item.key}
|
|
|
|
className={className}
|
|
|
|
style={style}
|
|
|
|
size={size}
|
|
|
|
onResizeStart={onResizeStart}
|
|
|
|
onResize={onResize}
|
|
|
|
onResizeStop={onResizeStop}
|
|
|
|
enable={enable}
|
|
|
|
minWidth={item.minWidth}
|
|
|
|
minHeight={item.minHeight}
|
|
|
|
>
|
|
|
|
{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-12 21:13:28 +02:00
|
|
|
function renderLayoutItem(item: LayoutItem, 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,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2020-11-12 21:13:28 +02:00
|
|
|
function onResize(_event: any, _direction: any, _refToElement: HTMLDivElement, delta: any) {
|
2020-09-15 15:01:07 +02:00
|
|
|
const newLayout = updateLayoutItem(props.layout, item.key, {
|
|
|
|
width: resizedItem.initialWidth + delta.width,
|
|
|
|
height: resizedItem.initialHeight + delta.height,
|
|
|
|
});
|
|
|
|
|
|
|
|
props.onResize({ layout: newLayout });
|
|
|
|
eventEmitter.current.emit('resize');
|
|
|
|
}
|
|
|
|
|
2020-11-12 21:13:28 +02:00
|
|
|
function onResizeStop(_event: any, _direction: any, _refToElement: HTMLDivElement, delta: any) {
|
2020-09-15 15:01:07 +02:00
|
|
|
onResize(_event, _direction, _refToElement, delta);
|
|
|
|
setResizedItem(null);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!item.children) {
|
|
|
|
const comp = props.renderItem(item.key, {
|
|
|
|
item: item,
|
|
|
|
eventEmitter: eventEmitter.current,
|
|
|
|
size: sizes[item.key],
|
|
|
|
visible: isVisible,
|
|
|
|
});
|
|
|
|
|
2020-10-09 19:35:46 +02:00
|
|
|
return renderContainer(item, sizes, onResizeStart, onResize, onResizeStop, [comp], isLastChild);
|
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];
|
|
|
|
childrenComponents.push(renderLayoutItem(child, sizes, isVisible && child.visible !== false, i === item.children.length - 1));
|
2020-09-15 15:01:07 +02:00
|
|
|
}
|
|
|
|
|
2020-10-09 19:35:46 +02:00
|
|
|
return renderContainer(item, sizes, onResizeStart, onResize, onResizeStop, childrenComponents, isLastChild);
|
2020-09-15 15:01:07 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
useWindowResizeEvent(eventEmitter);
|
|
|
|
const sizes = useLayoutItemSizes(props.layout);
|
|
|
|
|
2020-10-09 19:35:46 +02:00
|
|
|
return renderLayoutItem(props.layout, sizes, props.layout.visible !== false, true);
|
2020-09-15 15:01:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
export default ResizableLayout;
|