mirror of
https://github.com/laurent22/joplin.git
synced 2025-01-26 18:58:21 +02:00
parent
3f0586ef63
commit
4760e5e8ba
@ -2,7 +2,7 @@ import * as React from 'react';
|
|||||||
import { useRef, useState, useEffect } from 'react';
|
import { useRef, useState, useEffect } from 'react';
|
||||||
import useWindowResizeEvent from './utils/useWindowResizeEvent';
|
import useWindowResizeEvent from './utils/useWindowResizeEvent';
|
||||||
import setLayoutItemProps from './utils/setLayoutItemProps';
|
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 validateLayout from './utils/validateLayout';
|
||||||
import { Size, LayoutItem } from './utils/types';
|
import { Size, LayoutItem } from './utils/types';
|
||||||
import { canMove, MoveDirection } from './utils/movements';
|
import { canMove, MoveDirection } from './utils/movements';
|
||||||
@ -11,9 +11,6 @@ import { StyledWrapperRoot, StyledMoveOverlay, MoveModeRootWrapper, MoveModeRoot
|
|||||||
import { Resizable } from 're-resizable';
|
import { Resizable } from 're-resizable';
|
||||||
const EventEmitter = require('events');
|
const EventEmitter = require('events');
|
||||||
|
|
||||||
const itemMinWidth = 20;
|
|
||||||
const itemMinHeight = 20;
|
|
||||||
|
|
||||||
interface onResizeEvent {
|
interface onResizeEvent {
|
||||||
layout: LayoutItem;
|
layout: LayoutItem;
|
||||||
}
|
}
|
||||||
@ -35,7 +32,7 @@ function itemVisible(item: LayoutItem, moveMode: boolean) {
|
|||||||
return item.visible !== false;
|
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 = {
|
const style: any = {
|
||||||
display: itemVisible(item, moveMode) ? 'flex' : 'none',
|
display: itemVisible(item, moveMode) ? 'flex' : 'none',
|
||||||
flexDirection: item.direction,
|
flexDirection: item.direction,
|
||||||
@ -68,6 +65,8 @@ function renderContainer(item: LayoutItem, parent: LayoutItem | null, sizes: Lay
|
|||||||
enable={enable}
|
enable={enable}
|
||||||
minWidth={'minWidth' in item ? item.minWidth : itemMinWidth}
|
minWidth={'minWidth' in item ? item.minWidth : itemMinWidth}
|
||||||
minHeight={'minHeight' in item ? item.minHeight : itemMinHeight}
|
minHeight={'minHeight' in item ? item.minHeight : itemMinHeight}
|
||||||
|
maxWidth={resizedItemMaxSize?.width}
|
||||||
|
maxHeight={resizedItemMaxSize?.height}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</Resizable>
|
</Resizable>
|
||||||
@ -114,6 +113,7 @@ function ResizableLayout(props: Props) {
|
|||||||
key: item.key,
|
key: item.key,
|
||||||
initialWidth: sizes[item.key].width,
|
initialWidth: sizes[item.key].width,
|
||||||
initialHeight: sizes[item.key].height,
|
initialHeight: sizes[item.key].height,
|
||||||
|
maxSize: calculateMaxSizeAvailableForItem(item, parent, sizes),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -143,6 +143,7 @@ function ResizableLayout(props: Props) {
|
|||||||
setResizedItem(null);
|
setResizedItem(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const resizedItemMaxSize = item.key === resizedItem?.key ? resizedItem.maxSize : null;
|
||||||
if (!item.children) {
|
if (!item.children) {
|
||||||
const size = itemSize(item, parent, sizes, false);
|
const size = itemSize(item, parent, sizes, false);
|
||||||
|
|
||||||
@ -155,7 +156,7 @@ function ResizableLayout(props: Props) {
|
|||||||
|
|
||||||
const wrapper = renderItemWrapper(comp, item, parent, size, props.moveMode);
|
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 {
|
} else {
|
||||||
const childrenComponents = [];
|
const childrenComponents = [];
|
||||||
for (let i = 0; i < item.children.length; i++) {
|
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));
|
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 { LayoutItem, LayoutItemDirection } from './types';
|
||||||
import { renderHook } from '@testing-library/react-hooks';
|
import { renderHook } from '@testing-library/react-hooks';
|
||||||
import validateLayout from './validateLayout';
|
import validateLayout from './validateLayout';
|
||||||
@ -138,4 +138,219 @@ describe('useLayoutItemSizes', () => {
|
|||||||
expect(itemSize(parent.children[1], parent, sizes, false)).toEqual({ width: 95, height: 50 });
|
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;
|
const dragBarThickness = 5;
|
||||||
|
|
||||||
|
export const itemMinWidth = 40;
|
||||||
|
export const itemMinHeight = 40;
|
||||||
|
|
||||||
export interface LayoutItemSizes {
|
export interface LayoutItemSizes {
|
||||||
[key: string]: Size;
|
[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;
|
const bottomGap = !isContainer && (item.resizableBottom || parentResizableBottom) ? dragBarThickness : 0;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
width: ('width' in item ? item.width : sizes[item.key].width) - rightGap,
|
width: sizes[item.key].width - rightGap,
|
||||||
height: ('height' in item ? item.height : sizes[item.key].height) - bottomGap,
|
height: sizes[item.key].height - bottomGap,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -38,6 +41,10 @@ function calculateChildrenSizes(item: LayoutItem, parent: LayoutItem | null, siz
|
|||||||
const noWidthChildren: any[] = [];
|
const noWidthChildren: any[] = [];
|
||||||
const noHeightChildren: 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) {
|
for (const child of item.children) {
|
||||||
let w = 'width' in child ? child.width : null;
|
let w = 'width' in child ? child.width : null;
|
||||||
let h = 'height' in child ? child.height : 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 };
|
sizes[child.key] = { width: w, height: h };
|
||||||
|
|
||||||
if (w !== null) remainingSize.width -= w;
|
if (w !== null) remainingSize.width -= w;
|
||||||
if (h !== null) remainingSize.height -= h;
|
if (h !== null) remainingSize.height -= h;
|
||||||
if (w === null) noWidthChildren.push({ item: child, parent: item });
|
if (w === null) {
|
||||||
if (h === null) noHeightChildren.push({ item: child, parent: item });
|
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) {
|
if (noWidthChildren.length) {
|
||||||
@ -77,6 +117,24 @@ function calculateChildrenSizes(item: LayoutItem, parent: LayoutItem | null, siz
|
|||||||
return sizes;
|
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) {
|
export default function useLayoutItemSizes(layout: LayoutItem, makeAllVisible: boolean = false) {
|
||||||
return useMemo(() => {
|
return useMemo(() => {
|
||||||
let sizes: LayoutItemSizes = {};
|
let sizes: LayoutItemSizes = {};
|
||||||
|
Loading…
x
Reference in New Issue
Block a user