1
0
mirror of https://github.com/laurent22/joplin.git synced 2025-01-11 18:24:43 +02:00

Desktop: Fixes #4864: Fixes panels overflowing window (#4991)

This commit is contained in:
mbalint 2021-05-22 19:30:11 +02:00 committed by GitHub
parent 3f0586ef63
commit 4760e5e8ba
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 286 additions and 12 deletions

View File

@ -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);
}
}

View File

@ -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)
});
});

View File

@ -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 = {};