mirror of
https://github.com/laurent22/joplin.git
synced 2025-01-11 18:24:43 +02:00
parent
3f0586ef63
commit
4760e5e8ba
@ -2,7 +2,7 @@ import * as React from 'react';
|
||||
import { useRef, useState, useEffect } from 'react';
|
||||
import useWindowResizeEvent from './utils/useWindowResizeEvent';
|
||||
import setLayoutItemProps from './utils/setLayoutItemProps';
|
||||
import useLayoutItemSizes, { LayoutItemSizes, itemSize } from './utils/useLayoutItemSizes';
|
||||
import useLayoutItemSizes, { LayoutItemSizes, itemSize, calculateMaxSizeAvailableForItem, itemMinWidth, itemMinHeight } from './utils/useLayoutItemSizes';
|
||||
import validateLayout from './utils/validateLayout';
|
||||
import { Size, LayoutItem } from './utils/types';
|
||||
import { canMove, MoveDirection } from './utils/movements';
|
||||
@ -11,9 +11,6 @@ import { StyledWrapperRoot, StyledMoveOverlay, MoveModeRootWrapper, MoveModeRoot
|
||||
import { Resizable } from 're-resizable';
|
||||
const EventEmitter = require('events');
|
||||
|
||||
const itemMinWidth = 20;
|
||||
const itemMinHeight = 20;
|
||||
|
||||
interface onResizeEvent {
|
||||
layout: LayoutItem;
|
||||
}
|
||||
@ -35,7 +32,7 @@ function itemVisible(item: LayoutItem, moveMode: boolean) {
|
||||
return item.visible !== false;
|
||||
}
|
||||
|
||||
function renderContainer(item: LayoutItem, parent: LayoutItem | null, sizes: LayoutItemSizes, onResizeStart: Function, onResize: Function, onResizeStop: Function, children: any[], isLastChild: boolean, moveMode: boolean): any {
|
||||
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 {
|
||||
const style: any = {
|
||||
display: itemVisible(item, moveMode) ? 'flex' : 'none',
|
||||
flexDirection: item.direction,
|
||||
@ -68,6 +65,8 @@ function renderContainer(item: LayoutItem, parent: LayoutItem | null, sizes: Lay
|
||||
enable={enable}
|
||||
minWidth={'minWidth' in item ? item.minWidth : itemMinWidth}
|
||||
minHeight={'minHeight' in item ? item.minHeight : itemMinHeight}
|
||||
maxWidth={resizedItemMaxSize?.width}
|
||||
maxHeight={resizedItemMaxSize?.height}
|
||||
>
|
||||
{children}
|
||||
</Resizable>
|
||||
@ -114,6 +113,7 @@ function ResizableLayout(props: Props) {
|
||||
key: item.key,
|
||||
initialWidth: sizes[item.key].width,
|
||||
initialHeight: sizes[item.key].height,
|
||||
maxSize: calculateMaxSizeAvailableForItem(item, parent, sizes),
|
||||
});
|
||||
}
|
||||
|
||||
@ -143,6 +143,7 @@ function ResizableLayout(props: Props) {
|
||||
setResizedItem(null);
|
||||
}
|
||||
|
||||
const resizedItemMaxSize = item.key === resizedItem?.key ? resizedItem.maxSize : null;
|
||||
if (!item.children) {
|
||||
const size = itemSize(item, parent, sizes, false);
|
||||
|
||||
@ -155,7 +156,7 @@ function ResizableLayout(props: Props) {
|
||||
|
||||
const wrapper = renderItemWrapper(comp, item, parent, size, props.moveMode);
|
||||
|
||||
return renderContainer(item, parent, sizes, onResizeStart, onResize, onResizeStop, [wrapper], isLastChild, props.moveMode);
|
||||
return renderContainer(item, parent, sizes, resizedItemMaxSize, onResizeStart, onResize, onResizeStop, [wrapper], isLastChild, props.moveMode);
|
||||
} else {
|
||||
const childrenComponents = [];
|
||||
for (let i = 0; i < item.children.length; i++) {
|
||||
@ -163,7 +164,7 @@ function ResizableLayout(props: Props) {
|
||||
childrenComponents.push(renderLayoutItem(child, item, sizes, isVisible && itemVisible(child, props.moveMode), i === item.children.length - 1));
|
||||
}
|
||||
|
||||
return renderContainer(item, parent, sizes, onResizeStart, onResize, onResizeStop, childrenComponents, isLastChild, props.moveMode);
|
||||
return renderContainer(item, parent, sizes, resizedItemMaxSize, onResizeStart, onResize, onResizeStop, childrenComponents, isLastChild, props.moveMode);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import useLayoutItemSizes, { itemSize } from './useLayoutItemSizes';
|
||||
import useLayoutItemSizes, { itemSize, calculateMaxSizeAvailableForItem } from './useLayoutItemSizes';
|
||||
import { LayoutItem, LayoutItemDirection } from './types';
|
||||
import { renderHook } from '@testing-library/react-hooks';
|
||||
import validateLayout from './validateLayout';
|
||||
@ -138,4 +138,219 @@ describe('useLayoutItemSizes', () => {
|
||||
expect(itemSize(parent.children[1], parent, sizes, false)).toEqual({ width: 95, height: 50 });
|
||||
});
|
||||
|
||||
test('should decrease size of the largest item if the total size would be larger than the container', () => {
|
||||
const layout: LayoutItem = validateLayout({
|
||||
key: 'root',
|
||||
width: 200,
|
||||
height: 100,
|
||||
direction: LayoutItemDirection.Row,
|
||||
children: [
|
||||
{
|
||||
key: 'col1',
|
||||
width: 110,
|
||||
},
|
||||
{
|
||||
key: 'col2',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
key: 'col3',
|
||||
minWidth: 50,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const { result } = renderHook(() => useLayoutItemSizes(layout));
|
||||
const sizes = result.current;
|
||||
|
||||
expect(sizes.col1.width).toBe(50);
|
||||
expect(sizes.col2.width).toBe(100);
|
||||
expect(sizes.col3.width).toBe(50);
|
||||
});
|
||||
|
||||
test('should not allow a minWidth of 0, should still make space for the item', () => {
|
||||
const layout: LayoutItem = validateLayout({
|
||||
key: 'root',
|
||||
width: 200,
|
||||
height: 100,
|
||||
direction: LayoutItemDirection.Row,
|
||||
children: [
|
||||
{
|
||||
key: 'col1',
|
||||
width: 210,
|
||||
},
|
||||
{
|
||||
key: 'col2',
|
||||
minWidth: 0,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const { result } = renderHook(() => useLayoutItemSizes(layout));
|
||||
const sizes = result.current;
|
||||
|
||||
expect(sizes.col1.width).toBe(160);
|
||||
expect(sizes.col2.width).toBe(40); // default minWidth is 40
|
||||
});
|
||||
|
||||
test('should ignore invisible items when counting remaining size', () => {
|
||||
const layout: LayoutItem = validateLayout({
|
||||
key: 'root',
|
||||
width: 200,
|
||||
height: 100,
|
||||
direction: LayoutItemDirection.Row,
|
||||
children: [
|
||||
{
|
||||
key: 'col1',
|
||||
width: 110,
|
||||
visible: false,
|
||||
},
|
||||
{
|
||||
key: 'col2',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
key: 'col3',
|
||||
minWidth: 50,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const { result } = renderHook(() => useLayoutItemSizes(layout));
|
||||
const sizes = result.current;
|
||||
|
||||
expect(sizes.col1.width).toBe(0);
|
||||
expect(sizes.col2.width).toBe(100);
|
||||
expect(sizes.col3.width).toBe(100);
|
||||
});
|
||||
|
||||
test('should ignore invisible items when selecting largest child', () => {
|
||||
const layout: LayoutItem = validateLayout({
|
||||
key: 'root',
|
||||
width: 200,
|
||||
height: 100,
|
||||
direction: LayoutItemDirection.Row,
|
||||
children: [
|
||||
{
|
||||
key: 'col1',
|
||||
width: 110,
|
||||
visible: false,
|
||||
},
|
||||
{
|
||||
key: 'col2',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
key: 'col3',
|
||||
width: 110,
|
||||
},
|
||||
{
|
||||
key: 'col4',
|
||||
minWidth: 50,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const { result } = renderHook(() => useLayoutItemSizes(layout));
|
||||
const sizes = result.current;
|
||||
|
||||
expect(sizes.col1.width).toBe(0);
|
||||
expect(sizes.col2.width).toBe(100);
|
||||
expect(sizes.col3.width).toBe(50);
|
||||
expect(sizes.col4.width).toBe(50);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('calculateMaxSizeAvailableForItem', () => {
|
||||
|
||||
test('should give maximum available space this item can take up during resizing', () => {
|
||||
const layout: LayoutItem = validateLayout({
|
||||
key: 'root',
|
||||
width: 200,
|
||||
height: 100,
|
||||
direction: LayoutItemDirection.Row,
|
||||
children: [
|
||||
{
|
||||
key: 'col1',
|
||||
width: 50,
|
||||
},
|
||||
{
|
||||
key: 'col2',
|
||||
width: 70,
|
||||
},
|
||||
{
|
||||
key: 'col3',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const { result } = renderHook(() => useLayoutItemSizes(layout));
|
||||
const sizes = result.current;
|
||||
const maxSize1 = calculateMaxSizeAvailableForItem(layout.children[0], layout, sizes);
|
||||
const maxSize2 = calculateMaxSizeAvailableForItem(layout.children[1], layout, sizes);
|
||||
|
||||
// maxSize = totalSize - ( [size of items with set size, except for the current item] + [minimum size of items with no set size] )
|
||||
expect(maxSize1.width).toBe(90); // 90 = layout.width - (col2.width + col3.minWidth(=40) )
|
||||
expect(maxSize2.width).toBe(110); // 110 = layout.width - (col1.width + col3.minWidth(=40) )
|
||||
});
|
||||
|
||||
test('should respect minimum sizes', () => {
|
||||
const layout: LayoutItem = validateLayout({
|
||||
key: 'root',
|
||||
width: 200,
|
||||
height: 100,
|
||||
direction: LayoutItemDirection.Row,
|
||||
children: [
|
||||
{
|
||||
key: 'col1',
|
||||
width: 50,
|
||||
},
|
||||
{
|
||||
key: 'col2',
|
||||
width: 70,
|
||||
},
|
||||
{
|
||||
key: 'col3',
|
||||
minWidth: 60,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const { result } = renderHook(() => useLayoutItemSizes(layout));
|
||||
const sizes = result.current;
|
||||
const maxSize1 = calculateMaxSizeAvailableForItem(layout.children[0], layout, sizes);
|
||||
const maxSize2 = calculateMaxSizeAvailableForItem(layout.children[1], layout, sizes);
|
||||
|
||||
// maxSize = totalSize - ( [size of items with set size, except for the current item] + [minimum size of items with no set size] )
|
||||
expect(maxSize1.width).toBe(70); // 70 = layout.width - (col2.width + col3.minWidth)
|
||||
expect(maxSize2.width).toBe(90); // 90 = layout.width - (col1.width + col3.minWidth)
|
||||
});
|
||||
|
||||
test('should not allow a minWidth of 0, should still leave space for the item', () => {
|
||||
const layout: LayoutItem = validateLayout({
|
||||
key: 'root',
|
||||
width: 200,
|
||||
height: 100,
|
||||
direction: LayoutItemDirection.Row,
|
||||
children: [
|
||||
{
|
||||
key: 'col1',
|
||||
width: 50,
|
||||
},
|
||||
{
|
||||
key: 'col2',
|
||||
minWidth: 0,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const { result } = renderHook(() => useLayoutItemSizes(layout));
|
||||
const sizes = result.current;
|
||||
const maxSize1 = calculateMaxSizeAvailableForItem(layout.children[0], layout, sizes);
|
||||
|
||||
// maxSize = totalSize - ( [size of items with set size, except for the current item] + [minimum size of items with no set size] )
|
||||
expect(maxSize1.width).toBe(160); // 160 = layout.width - col2.minWidth(=40)
|
||||
});
|
||||
|
||||
});
|
||||
|
@ -3,6 +3,9 @@ import { LayoutItem, Size } from './types';
|
||||
|
||||
const dragBarThickness = 5;
|
||||
|
||||
export const itemMinWidth = 40;
|
||||
export const itemMinHeight = 40;
|
||||
|
||||
export interface LayoutItemSizes {
|
||||
[key: string]: Size;
|
||||
}
|
||||
@ -17,8 +20,8 @@ export function itemSize(item: LayoutItem, parent: LayoutItem | null, sizes: Lay
|
||||
const bottomGap = !isContainer && (item.resizableBottom || parentResizableBottom) ? dragBarThickness : 0;
|
||||
|
||||
return {
|
||||
width: ('width' in item ? item.width : sizes[item.key].width) - rightGap,
|
||||
height: ('height' in item ? item.height : sizes[item.key].height) - bottomGap,
|
||||
width: sizes[item.key].width - rightGap,
|
||||
height: sizes[item.key].height - bottomGap,
|
||||
};
|
||||
}
|
||||
|
||||
@ -38,6 +41,10 @@ function calculateChildrenSizes(item: LayoutItem, parent: LayoutItem | null, siz
|
||||
const noWidthChildren: any[] = [];
|
||||
const noHeightChildren: any[] = [];
|
||||
|
||||
// The minimum space required for items with no defined size
|
||||
let noWidthChildrenMinWidth = 0;
|
||||
let noHeightChildrenMinHeight = 0;
|
||||
|
||||
for (const child of item.children) {
|
||||
let w = 'width' in child ? child.width : null;
|
||||
let h = 'height' in child ? child.height : null;
|
||||
@ -47,10 +54,43 @@ function calculateChildrenSizes(item: LayoutItem, parent: LayoutItem | null, siz
|
||||
}
|
||||
|
||||
sizes[child.key] = { width: w, height: h };
|
||||
|
||||
if (w !== null) remainingSize.width -= w;
|
||||
if (h !== null) remainingSize.height -= h;
|
||||
if (w === null) noWidthChildren.push({ item: child, parent: item });
|
||||
if (h === null) noHeightChildren.push({ item: child, parent: item });
|
||||
if (w === null) {
|
||||
noWidthChildren.push({ item: child, parent: item });
|
||||
noWidthChildrenMinWidth += child.minWidth || itemMinWidth;
|
||||
}
|
||||
if (h === null) {
|
||||
noHeightChildren.push({ item: child, parent: item });
|
||||
noHeightChildrenMinHeight += child.minHeight || itemMinHeight;
|
||||
}
|
||||
}
|
||||
|
||||
while (remainingSize.width < noWidthChildrenMinWidth) {
|
||||
// There is not enough space, the widest item will be made smaller
|
||||
let widestChild = item.children[0].key;
|
||||
for (const child of item.children) {
|
||||
if (!child.visible) continue;
|
||||
if (sizes[child.key].width > sizes[widestChild].width) widestChild = child.key;
|
||||
}
|
||||
|
||||
const dw = Math.abs(remainingSize.width - noWidthChildrenMinWidth);
|
||||
sizes[widestChild].width -= dw;
|
||||
remainingSize.width += dw;
|
||||
}
|
||||
|
||||
while (remainingSize.height < noHeightChildrenMinHeight) {
|
||||
// There is not enough space, the tallest item will be made smaller
|
||||
let tallestChild = item.children[0].key;
|
||||
for (const child of item.children) {
|
||||
if (!child.visible) continue;
|
||||
if (sizes[child.key].height > sizes[tallestChild].height) tallestChild = child.key;
|
||||
}
|
||||
|
||||
const dh = Math.abs(remainingSize.height - noHeightChildrenMinHeight);
|
||||
sizes[tallestChild].height -= dh;
|
||||
remainingSize.height += dh;
|
||||
}
|
||||
|
||||
if (noWidthChildren.length) {
|
||||
@ -77,6 +117,24 @@ function calculateChildrenSizes(item: LayoutItem, parent: LayoutItem | null, siz
|
||||
return sizes;
|
||||
}
|
||||
|
||||
// Gives the maximum available space for this item that it can take up during resizing
|
||||
// availableSize = totalSize - ( [size of items with set size, except for the current item] + [minimum size of items with no set size] )
|
||||
export function calculateMaxSizeAvailableForItem(item: LayoutItem, parent: LayoutItem, sizes: LayoutItemSizes): Size {
|
||||
const availableSize: Size = { ...sizes[parent.key] };
|
||||
|
||||
for (const sibling of parent.children) {
|
||||
if (!sibling.visible) continue;
|
||||
|
||||
availableSize.width -= 'width' in sibling ? sizes[sibling.key].width : (sibling.minWidth || itemMinWidth);
|
||||
availableSize.height -= 'height' in sibling ? sizes[sibling.key].height : (sibling.minHeight || itemMinHeight);
|
||||
}
|
||||
|
||||
availableSize.width += sizes[item.key].width;
|
||||
availableSize.height += sizes[item.key].height;
|
||||
|
||||
return availableSize;
|
||||
}
|
||||
|
||||
export default function useLayoutItemSizes(layout: LayoutItem, makeAllVisible: boolean = false) {
|
||||
return useMemo(() => {
|
||||
let sizes: LayoutItemSizes = {};
|
||||
|
Loading…
Reference in New Issue
Block a user