mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
feat(ui): bump react-resizable-panels, improve panel resize logic
This commit is contained in:
parent
2663a07e94
commit
2ffecef792
@ -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",
|
||||||
|
8
invokeai/frontend/web/pnpm-lock.yaml
generated
8
invokeai/frontend/web/pnpm-lock.yaml
generated
@ -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
|
||||||
|
@ -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}
|
||||||
|
@ -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();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user