1
0
mirror of https://github.com/laurent22/joplin.git synced 2024-12-15 09:04:04 +02:00
joplin/ElectronClient/gui/ResizableLayout/ResizableLayout.tsx

216 lines
5.4 KiB
TypeScript

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');
export const dragBarThickness = 5;
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,
resizableRight?: boolean,
resizableBottom?: boolean,
visible?: boolean,
context?: any,
}
interface onResizeEvent {
layout: LayoutItem
}
interface Props {
layout: LayoutItem,
onResize(event:onResizeEvent):void;
width?: number,
height?: number,
renderItem: Function,
}
export function allDynamicSizes(layout:LayoutItem):any {
const output:any = {};
function recurseProcess(item:LayoutItem) {
if (item.resizableBottom || item.resizableRight) {
if ('width' in item || 'height' in item) {
const size:any = {};
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;
}
export function findItemByKey(layout:LayoutItem, key:string):LayoutItem {
function recurseFind(item:LayoutItem):LayoutItem {
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;
}
function updateLayoutItem(layout:LayoutItem, key:string, props:any) {
return produce(layout, (draftState:LayoutItem) => {
function recurseFind(item:LayoutItem) {
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);
});
}
function renderContainer(item:LayoutItem, sizes:LayoutItemSizes, onResizeStart:Function, onResize:Function, onResizeStop:Function, children:JSX.Element[], isLastChild:boolean):JSX.Element {
const style:any = {
display: item.visible !== false ? 'flex' : 'none',
flexDirection: item.direction,
};
const size:Size = itemSize(item, sizes);
const className = `resizableLayoutItem rli-${item.key}`;
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;
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>
);
}
}
function ResizableLayout(props:Props) {
const eventEmitter = useRef(new EventEmitter());
const [resizedItem, setResizedItem] = useState<any>(null);
function renderLayoutItem(item:LayoutItem, sizes:LayoutItemSizes, isVisible:boolean, isLastChild:boolean):JSX.Element {
function onResizeStart() {
setResizedItem({
key: item.key,
initialWidth: sizes[item.key].width,
initialHeight: sizes[item.key].height,
});
}
function onResize(_event:any, _direction:any, _refToElement: HTMLDivElement, delta:any) {
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');
}
function onResizeStop(_event:any, _direction:any, _refToElement: HTMLDivElement, delta:any) {
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,
});
return renderContainer(item, sizes, onResizeStart, onResize, onResizeStop, [comp], isLastChild);
} else {
const childrenComponents = [];
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));
}
return renderContainer(item, sizes, onResizeStart, onResize, onResizeStop, childrenComponents, isLastChild);
}
}
useWindowResizeEvent(eventEmitter);
const sizes = useLayoutItemSizes(props.layout);
return renderLayoutItem(props.layout, sizes, props.layout.visible !== false, true);
}
export default ResizableLayout;