feat(ui): bump react-resizable-panels, improve panel resize logic

This commit is contained in:
psychedelicious 2023-12-31 13:05:41 +11:00 committed by Kent Keirsey
parent 2663a07e94
commit 2ffecef792
4 changed files with 78 additions and 45 deletions

View File

@ -95,7 +95,7 @@
"react-icons": "^4.12.0", "react-icons": "^4.12.0",
"react-konva": "^18.2.10", "react-konva": "^18.2.10",
"react-redux": "^9.0.4", "react-redux": "^9.0.4",
"react-resizable-panels": "^1.0.5", "react-resizable-panels": "^1.0.6",
"react-select": "5.7.7", "react-select": "5.7.7",
"react-textarea-autosize": "^8.5.3", "react-textarea-autosize": "^8.5.3",
"react-use": "^17.4.2", "react-use": "^17.4.2",

View File

@ -135,8 +135,8 @@ dependencies:
specifier: ^9.0.4 specifier: ^9.0.4
version: 9.0.4(@types/react@18.2.46)(react@18.2.0)(redux@5.0.1) version: 9.0.4(@types/react@18.2.46)(react@18.2.0)(redux@5.0.1)
react-resizable-panels: react-resizable-panels:
specifier: ^1.0.5 specifier: ^1.0.6
version: 1.0.5(react-dom@18.2.0)(react@18.2.0) version: 1.0.6(react-dom@18.2.0)(react@18.2.0)
react-select: react-select:
specifier: 5.7.7 specifier: 5.7.7
version: 5.7.7(@types/react@18.2.46)(react-dom@18.2.0)(react@18.2.0) version: 5.7.7(@types/react@18.2.46)(react-dom@18.2.0)(react@18.2.0)
@ -11221,8 +11221,8 @@ packages:
use-sidecar: 1.1.2(@types/react@18.2.46)(react@18.2.0) use-sidecar: 1.1.2(@types/react@18.2.46)(react@18.2.0)
dev: false dev: false
/react-resizable-panels@1.0.5(react-dom@18.2.0)(react@18.2.0): /react-resizable-panels@1.0.6(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-OP0whNQCko+f4BgoptGaeIc7StBRyeMeJ+8r/7rXACBDf9W5EcMWuM32hfqPDMenS2HFy/eZVi/r8XqK+ZIEag==} resolution: {integrity: sha512-yZQiOP/uW2nTSdESDUBlBkQ1NQjUABpRKfBqonUQnNYSur7qRDy3W2wEEmrEyUY+W3opshpMiHf45zngIduJ0g==}
peerDependencies: peerDependencies:
react: ^16.14.0 || ^17.0.0 || ^18.0.0 react: ^16.14.0 || ^17.0.0 || ^18.0.0
react-dom: ^16.14.0 || ^17.0.0 || ^18.0.0 react-dom: ^16.14.0 || ^17.0.0 || ^18.0.0

View File

@ -104,19 +104,7 @@ const GALLERY_MIN_SIZE_PCT = 20;
const OPTIONS_PANEL_MIN_SIZE_PX = 430; const OPTIONS_PANEL_MIN_SIZE_PX = 430;
const OPTIONS_PANEL_MIN_SIZE_PCT = 20; const OPTIONS_PANEL_MIN_SIZE_PCT = 20;
const optionsPanelUsePanelOptions: UsePanelOptions = { const appPanelGroupId = 'app-panel-group';
unit: 'pixels',
minSize: OPTIONS_PANEL_MIN_SIZE_PX,
fallbackMinSizePct: OPTIONS_PANEL_MIN_SIZE_PCT,
panelGroupID: 'app',
};
const galleryPanelUsePanelOptions: UsePanelOptions = {
unit: 'pixels',
minSize: GALLERY_MIN_SIZE_PX,
fallbackMinSizePct: GALLERY_MIN_SIZE_PCT,
panelGroupID: 'app',
};
const InvokeTabs = () => { const InvokeTabs = () => {
const activeTabIndex = useAppSelector(activeTabIndexSelector); const activeTabIndex = useAppSelector(activeTabIndexSelector);
@ -124,7 +112,7 @@ const InvokeTabs = () => {
const enabledTabs = useAppSelector(enabledTabsSelector); const enabledTabs = useAppSelector(enabledTabsSelector);
const { t } = useTranslation(); const { t } = useTranslation();
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const panelGroupHandleRef = useRef<ImperativePanelGroupHandle>(null); const panelGroupRef = useRef<ImperativePanelGroupHandle>(null);
const handleClickTab = useCallback((e: MouseEvent<HTMLElement>) => { const handleClickTab = useCallback((e: MouseEvent<HTMLElement>) => {
if (e.target instanceof HTMLElement) { if (e.target instanceof HTMLElement) {
e.target.blur(); e.target.blur();
@ -170,6 +158,28 @@ const InvokeTabs = () => {
[dispatch, enabledTabs] [dispatch, enabledTabs]
); );
const optionsPanelUsePanelOptions = useMemo<UsePanelOptions>(
() => ({
unit: 'pixels',
minSize: OPTIONS_PANEL_MIN_SIZE_PX,
fallbackMinSizePct: OPTIONS_PANEL_MIN_SIZE_PCT,
panelGroupRef,
panelGroupDirection: 'horizontal',
}),
[]
);
const galleryPanelUsePanelOptions = useMemo<UsePanelOptions>(
() => ({
unit: 'pixels',
minSize: GALLERY_MIN_SIZE_PX,
fallbackMinSizePct: GALLERY_MIN_SIZE_PCT,
panelGroupRef,
panelGroupDirection: 'horizontal',
}),
[]
);
const panelStorage = usePanelStorage(); const panelStorage = usePanelStorage();
const { const {
@ -227,8 +237,8 @@ const InvokeTabs = () => {
<Spacer /> <Spacer />
</InvTabList> </InvTabList>
<PanelGroup <PanelGroup
ref={panelGroupHandleRef} ref={panelGroupRef}
id="app" id={appPanelGroupId}
autoSaveId="app" autoSaveId="app"
direction="horizontal" direction="horizontal"
style={panelStyles} style={panelStyles}
@ -237,7 +247,7 @@ const InvokeTabs = () => {
{!NO_SIDE_PANEL_TABS.includes(activeTabName) && ( {!NO_SIDE_PANEL_TABS.includes(activeTabName) && (
<> <>
<Panel <Panel
id="options" id="options-panel"
ref={optionsPanelRef} ref={optionsPanelRef}
order={0} order={0}
defaultSize={optionsPanelMinSize} defaultSize={optionsPanelMinSize}
@ -253,12 +263,13 @@ const InvokeTabs = () => {
)} )}
</Panel> </Panel>
<ResizeHandle <ResizeHandle
id="options-main-handle"
onDoubleClick={resetOptionsPanel} onDoubleClick={resetOptionsPanel}
orientation="vertical" orientation="vertical"
/> />
</> </>
)} )}
<Panel id="main" order={1} minSize={20}> <Panel id="main-panel" order={1} minSize={20}>
<InvTabPanels w="full" h="full"> <InvTabPanels w="full" h="full">
{tabPanels} {tabPanels}
</InvTabPanels> </InvTabPanels>
@ -266,11 +277,12 @@ const InvokeTabs = () => {
{!NO_GALLERY_TABS.includes(activeTabName) && ( {!NO_GALLERY_TABS.includes(activeTabName) && (
<> <>
<ResizeHandle <ResizeHandle
id="main-gallery-handle"
onDoubleClick={resetGalleryPanel} onDoubleClick={resetGalleryPanel}
orientation="vertical" orientation="vertical"
/> />
<Panel <Panel
id="gallery" id="gallery-panel"
ref={galleryPanelRef} ref={galleryPanelRef}
order={2} order={2}
defaultSize={galleryPanelMinSize} defaultSize={galleryPanelMinSize}

View File

@ -1,9 +1,15 @@
import type { RefObject } from 'react';
import { useCallback, useLayoutEffect, useRef, useState } from 'react'; import { useCallback, useLayoutEffect, useRef, useState } from 'react';
import type { import type {
ImperativePanelGroupHandle,
ImperativePanelHandle, ImperativePanelHandle,
PanelOnCollapse, PanelOnCollapse,
PanelOnExpand, PanelOnExpand,
} from 'react-resizable-panels'; } from 'react-resizable-panels';
import {
getPanelGroupElement,
getResizeHandleElementsForGroup,
} from 'react-resizable-panels';
export type UsePanelOptions = export type UsePanelOptions =
| { minSize: number; unit: 'percentages' } | { minSize: number; unit: 'percentages' }
@ -11,46 +17,61 @@ export type UsePanelOptions =
minSize: number; minSize: number;
unit: 'pixels'; unit: 'pixels';
fallbackMinSizePct: number; fallbackMinSizePct: number;
panelGroupID: string; panelGroupRef: RefObject<ImperativePanelGroupHandle>;
panelGroupDirection: 'horizontal' | 'vertical';
}; };
export const usePanel = (arg: UsePanelOptions) => { export const usePanel = (arg: UsePanelOptions) => {
const panelHandleRef = useRef<ImperativePanelHandle>(null); const panelHandleRef = useRef<ImperativePanelHandle>(null);
const newMinSizeRef = useRef<number>(0);
const currentSizeRef = useRef<number>(0);
const [_minSize, _setMinSize] = useState<number>( const [_minSize, _setMinSize] = useState<number>(
arg.unit === 'percentages' ? arg.minSize : arg.fallbackMinSizePct arg.unit === 'percentages' ? arg.minSize : arg.fallbackMinSizePct
); );
// If the units are pixels, we need to calculate the min size as a percentage of the available space
useLayoutEffect(() => { useLayoutEffect(() => {
if (arg.unit === 'percentages') { if (arg.unit === 'percentages' || !arg.panelGroupRef.current) {
return; return;
} }
const panelGroupElement = document.querySelector( const panelGroupElement = getPanelGroupElement(
`[data-panel-group][data-panel-group-id="${arg.panelGroupID}"]` arg.panelGroupRef.current.getId()
);
const panelGroupHandleElements = getResizeHandleElementsForGroup(
arg.panelGroupRef.current.getId()
); );
if (!panelGroupElement) { if (!panelGroupElement) {
return; return;
} }
const resizeObserver = new ResizeObserver((entries) => { const resizeObserver = new ResizeObserver(() => {
for (const entry of entries) { if (!panelHandleRef?.current) {
if (!panelHandleRef?.current) { return;
return;
}
newMinSizeRef.current = (arg.minSize * 100) / entry.contentRect.width;
currentSizeRef.current =
panelHandleRef.current.getSize() ?? arg.fallbackMinSizePct;
if (currentSizeRef.current < newMinSizeRef.current) {
panelHandleRef.current.resize(newMinSizeRef.current);
}
_setMinSize(newMinSizeRef.current);
} }
// Calculate the available space for the panel, minus the space taken by the handles
let dim =
arg.panelGroupDirection === 'horizontal'
? panelGroupElement.offsetWidth
: panelGroupElement.offsetHeight;
panelGroupHandleElements.forEach(
(el) =>
(dim -=
arg.panelGroupDirection === 'horizontal'
? el.offsetWidth
: el.offsetHeight)
);
// Calculate the min size as a percentage of the available space
const minSize = (arg.minSize * 100) / dim;
// Must store this to avoid race conditions
const currentSize = panelHandleRef.current.getSize();
// Resize if the current size is smaller than the new min size - happens when the window is resized smaller
if (currentSize < minSize) {
panelHandleRef.current.resize(minSize);
}
_setMinSize(minSize);
}); });
resizeObserver.observe(panelGroupElement); resizeObserver.observe(panelGroupElement);
// _setMinSize(
// (arg.minSize * 100) / panelGroupElement.getBoundingClientRect().width
// );
return () => { return () => {
resizeObserver.disconnect(); resizeObserver.disconnect();