diff --git a/invokeai/frontend/web/package.json b/invokeai/frontend/web/package.json index 3cf8ba070a..561efdd8e9 100644 --- a/invokeai/frontend/web/package.json +++ b/invokeai/frontend/web/package.json @@ -89,6 +89,7 @@ "react-konva": "^18.2.7", "react-konva-utils": "^1.0.4", "react-redux": "^8.0.5", + "react-resizable-panels": "^0.0.42", "react-rnd": "^10.4.1", "react-transition-group": "^4.4.5", "react-use": "^17.4.0", diff --git a/invokeai/frontend/web/src/app/components/App.tsx b/invokeai/frontend/web/src/app/components/App.tsx index 3aebfa4097..c5f2c6ff04 100644 --- a/invokeai/frontend/web/src/app/components/App.tsx +++ b/invokeai/frontend/web/src/app/components/App.tsx @@ -1,6 +1,6 @@ import ImageUploader from 'common/components/ImageUploader'; -import ProgressBar from 'features/system/components/ProgressBar'; import SiteHeader from 'features/system/components/SiteHeader'; +import ProgressBar from 'features/system/components/ProgressBar'; import InvokeTabs from 'features/ui/components/InvokeTabs'; import useToastWatcher from 'features/system/hooks/useToastWatcher'; @@ -28,6 +28,9 @@ import { configChanged } from 'features/system/store/configSlice'; import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus'; import { useLogger } from 'app/logging/useLogger'; import ProgressImagePreview from 'features/parameters/components/ProgressImagePreview'; +import ResizableDrawer from 'features/ui/components/common/ResizableDrawer/ResizableDrawer'; +import ImageGalleryContent from 'features/gallery/components/ImageGalleryContent'; +import CreateParametersDrawer from 'features/ui/components/CreateParametersDrawer'; const DEFAULT_CONFIG = {}; @@ -84,11 +87,13 @@ const App = ({ config = DEFAULT_CONFIG, children }: Props) => { flexDir={{ base: 'column', xl: 'row' }} > - + + + {!isApplicationReady && !loadingOverridden && ( { { keyup: true, keydown: true }, [shift] ); + + useHotkeys('o', () => { + dispatch(toggleParametersPanel()); + }); + + useHotkeys(['shift+o'], () => { + dispatch(togglePinParametersPanel()); + }); + + useHotkeys('g', () => { + dispatch(toggleGalleryPanel()); + }); + + useHotkeys(['shift+g'], () => { + dispatch(togglePinGalleryPanel()); + }); }; diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageGalleryPanel.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageGalleryPanel.tsx index 76442d340b..24ff1d72af 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageGalleryPanel.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageGalleryPanel.tsx @@ -73,7 +73,7 @@ const galleryPanelSelector = createSelector( } ); -export const ImageGalleryPanel = () => { +const ImageGalleryPanel = () => { const dispatch = useAppDispatch(); const { shouldPinGallery, @@ -102,21 +102,21 @@ export const ImageGalleryPanel = () => { const resolution = useResolution(); - useHotkeys( - 'g', - () => { - handleToggleGallery(); - }, - [shouldPinGallery] - ); + // useHotkeys( + // 'g', + // () => { + // handleToggleGallery(); + // }, + // [shouldPinGallery] + // ); - useHotkeys( - 'shift+g', - () => { - handleSetShouldPinGallery(); - }, - [shouldPinGallery] - ); + // useHotkeys( + // 'shift+g', + // () => { + // handleSetShouldPinGallery(); + // }, + // [shouldPinGallery] + // ); useHotkeys( 'esc', @@ -162,55 +162,71 @@ export const ImageGalleryPanel = () => { [galleryImageMinimumWidth] ); - const calcGalleryMinHeight = () => { - if (resolution === 'desktop') return; - return 300; - }; + // const calcGalleryMinHeight = () => { + // if (resolution === 'desktop') return; + // return 300; + // }; - const imageGalleryContent = () => { - return ( - - - - ); - }; + // const imageGalleryContent = () => { + // return ( + // + // + // + // ); + // }; - const resizableImageGalleryContent = () => { - return ( - - - - ); - }; + // const resizableImageGalleryContent = () => { + // return ( + // + // + // + // ); + // }; - const renderImageGallery = () => { - if (['mobile', 'tablet'].includes(resolution)) return imageGalleryContent(); - return resizableImageGalleryContent(); - }; + // const renderImageGallery = () => { + // if (['mobile', 'tablet'].includes(resolution)) return imageGalleryContent(); + // return resizableImageGalleryContent(); + // }; - return renderImageGallery(); + if (shouldPinGallery) { + return null; + } + + return ( + + + + ); + + // return renderImageGallery(); }; export default memo(ImageGalleryPanel); diff --git a/invokeai/frontend/web/src/features/nodes/components/InputFieldComponent.tsx b/invokeai/frontend/web/src/features/nodes/components/InputFieldComponent.tsx index 4a1fa89eff..98698e2d81 100644 --- a/invokeai/frontend/web/src/features/nodes/components/InputFieldComponent.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/InputFieldComponent.tsx @@ -10,7 +10,6 @@ import ConditioningInputFieldComponent from './fields/ConditioningInputFieldComp import ModelInputFieldComponent from './fields/ModelInputFieldComponent'; import NumberInputFieldComponent from './fields/NumberInputFieldComponent'; import StringInputFieldComponent from './fields/StringInputFieldComponent'; -import ItemInputFieldComponent from './fields/ItemInputFieldComponent'; import ColorInputFieldComponent from './fields/ColorInputFieldComponent'; import ItemInputFieldComponent from './fields/ItemInputFieldComponent'; diff --git a/invokeai/frontend/web/src/features/parameters/components/AnimatedImageToImagePanel.tsx b/invokeai/frontend/web/src/features/parameters/components/AnimatedImageToImagePanel.tsx index da9262ac0f..8778e043e6 100644 --- a/invokeai/frontend/web/src/features/parameters/components/AnimatedImageToImagePanel.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/AnimatedImageToImagePanel.tsx @@ -20,7 +20,7 @@ const AnimatedImageToImagePanel = () => { exit={{ opacity: 0, scale: 0, width: 0 }} transition={{ type: 'spring', bounce: 0, duration: 0.35 }} > - + diff --git a/invokeai/frontend/web/src/features/parameters/components/ProcessButtons/CancelButton.tsx b/invokeai/frontend/web/src/features/parameters/components/ProcessButtons/CancelButton.tsx index 4f6c2ecc1c..03e9643384 100644 --- a/invokeai/frontend/web/src/features/parameters/components/ProcessButtons/CancelButton.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/ProcessButtons/CancelButton.tsx @@ -11,7 +11,7 @@ import { CancelStrategy, } from 'features/system/store/systemSlice'; import { isEqual } from 'lodash-es'; -import { useCallback, memo } from 'react'; +import { useCallback, memo, useMemo } from 'react'; import { ButtonSpinner, ButtonGroup, @@ -102,39 +102,39 @@ const CancelButton = ( [isConnected, isProcessing, isCancelable] ); + const cancelLabel = useMemo(() => { + if (isCancelScheduled) { + return t('parameters.cancel.isScheduled'); + } + if (cancelType === 'immediate') { + return t('parameters.cancel.immediate'); + } + + return t('parameters.cancel.schedule'); + }, [t, cancelType, isCancelScheduled]); + + const cancelIcon = useMemo(() => { + if (isCancelScheduled) { + return ; + } + if (cancelType === 'immediate') { + return ; + } + + return ; + }, [cancelType, isCancelScheduled]); + return ( - {cancelType === 'immediate' ? ( - } - tooltip={t('parameters.cancel.immediate')} - aria-label={t('parameters.cancel.immediate')} - isDisabled={!isConnected || !isProcessing || !isCancelable} - onClick={handleClickCancel} - colorScheme="error" - {...rest} - /> - ) : ( - : - } - tooltip={ - isCancelScheduled - ? t('parameters.cancel.isScheduled') - : t('parameters.cancel.schedule') - } - aria-label={ - isCancelScheduled - ? t('parameters.cancel.isScheduled') - : t('parameters.cancel.schedule') - } - isDisabled={!isConnected || !isProcessing || !isCancelable} - onClick={handleClickCancel} - colorScheme="error" - {...rest} - /> - )} + { + const { shouldPinParametersPanel, shouldShowParametersPanel } = ui; + const { isLightboxOpen } = lightbox; + + return { + shouldPinParametersPanel, + shouldShowParametersPanel, + }; + }, + { + memoizeOptions: { + resultEqualityCheck: isEqual, + }, + } +); + +const CreateParametersPanel = () => { + const dispatch = useAppDispatch(); + const { shouldPinParametersPanel, shouldShowParametersPanel } = + useAppSelector(selector); + + const handleClosePanel = () => { + dispatch(setShouldShowParametersPanel(false)); + }; + + if (shouldPinParametersPanel) { + return null; + } + + return ( + + + + + + + + + + ); +}; + +export default memo(CreateParametersPanel); diff --git a/invokeai/frontend/web/src/features/ui/components/FloatingGalleryButton.tsx b/invokeai/frontend/web/src/features/ui/components/FloatingGalleryButton.tsx index 83a699aca0..bcbcc1cefc 100644 --- a/invokeai/frontend/web/src/features/ui/components/FloatingGalleryButton.tsx +++ b/invokeai/frontend/web/src/features/ui/components/FloatingGalleryButton.tsx @@ -16,7 +16,7 @@ const floatingGalleryButtonSelector = createSelector( return { shouldPinGallery, - shouldShowGalleryButton: !shouldPinGallery || !shouldShowGallery, + shouldShowGalleryButton: !shouldShowGallery, }; }, { memoizeOptions: { resultEqualityCheck: isEqual } } diff --git a/invokeai/frontend/web/src/features/ui/components/FloatingParametersPanelButtons.tsx b/invokeai/frontend/web/src/features/ui/components/FloatingParametersPanelButtons.tsx index 3055216b66..90d199076d 100644 --- a/invokeai/frontend/web/src/features/ui/components/FloatingParametersPanelButtons.tsx +++ b/invokeai/frontend/web/src/features/ui/components/FloatingParametersPanelButtons.tsx @@ -39,7 +39,7 @@ export const floatingParametersPanelButtonSelector = createSelector( const shouldShowParametersPanelButton = !canvasBetaLayoutCheck && - (!shouldPinParametersPanel || !shouldShowParametersPanel) && + !shouldShowParametersPanel && ['generate', 'unifiedCanvas'].includes(activeTabName); return { @@ -65,7 +65,11 @@ const FloatingParametersPanelButtons = () => { shouldPinParametersPanel && dispatch(requestCanvasRescale()); }; - return shouldShowParametersPanelButton ? ( + if (!shouldShowParametersPanelButton) { + return null; + } + + return ( { )} - ) : null; + ); }; export default memo(FloatingParametersPanelButtons); diff --git a/invokeai/frontend/web/src/features/ui/components/InvokeTabs.tsx b/invokeai/frontend/web/src/features/ui/components/InvokeTabs.tsx index 17a84dd8f2..f548744c44 100644 --- a/invokeai/frontend/web/src/features/ui/components/InvokeTabs.tsx +++ b/invokeai/frontend/web/src/features/ui/components/InvokeTabs.tsx @@ -1,5 +1,7 @@ import { + Box, ChakraProps, + Flex, Icon, Tab, TabList, @@ -14,7 +16,14 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { setIsLightboxOpen } from 'features/lightbox/store/lightboxSlice'; import { InvokeTabName } from 'features/ui/store/tabMap'; import { setActiveTab, togglePanels } from 'features/ui/store/uiSlice'; -import { memo, ReactNode, useMemo } from 'react'; +import { + memo, + ReactNode, + useLayoutEffect, + useMemo, + useRef, + useState, +} from 'react'; import { useHotkeys } from 'react-hotkeys-hook'; import { MdDeviceHub, MdGridOn } from 'react-icons/md'; import { activeTabIndexSelector } from '../store/uiSelectors'; @@ -23,33 +32,47 @@ import { useTranslation } from 'react-i18next'; import { ResourceKey } from 'i18next'; import { requestCanvasRescale } from 'features/canvas/store/thunks/requestCanvasScale'; import NodeEditor from 'features/nodes/components/NodeEditor'; -import GenerateWorkspace from './tabs/Generate/GenerateWorkspace'; +import GenerateWorkspace from './tabs/Create/GenerateWorkspace'; import { createSelector } from '@reduxjs/toolkit'; import { BsLightningChargeFill } from 'react-icons/bs'; import { configSelector } from 'features/system/store/configSelectors'; import { isEqual } from 'lodash-es'; +import AnimatedImageToImagePanel from 'features/parameters/components/AnimatedImageToImagePanel'; +import Scrollable from './common/Scrollable'; +import GenerateParameters from './tabs/Create/GenerateParameters'; +import PinParametersPanelButton from './PinParametersPanelButton'; +import ParametersSlide from './common/ParametersSlide'; +import ImageGalleryPanel from 'features/gallery/components/ImageGalleryPanel'; +import { Panel, PanelGroup, PanelResizeHandle } from 'react-resizable-panels'; +import ImageGalleryContent from 'features/gallery/components/ImageGalleryContent'; +import CreateTabContent from './tabs/Create/GenerateContent'; +import ParametersPanel from './ParametersPanel'; +import { OverlayScrollbarsComponent } from 'overlayscrollbars-react'; +import CreateTab from './tabs/Create/CreateTab'; +import UnifiedCanvasTab from './tabs/UnifiedCanvas/UnifiedCanvasTab'; +import NodesTab from './tabs/Nodes/NodesTab'; export interface InvokeTabInfo { id: InvokeTabName; icon: ReactNode; - workarea: ReactNode; + content: ReactNode; } const tabs: InvokeTabInfo[] = [ { id: 'generate', icon: , - workarea: , + content: , }, { id: 'unifiedCanvas', icon: , - workarea: , + content: , }, { id: 'nodes', icon: , - workarea: , + content: , }, ]; @@ -72,9 +95,12 @@ const InvokeTabs = () => { (state: RootState) => state.lightbox.isLightboxOpen ); - const { shouldPinGallery, shouldPinParametersPanel } = useAppSelector( - (state: RootState) => state.ui - ); + const { + shouldPinGallery, + shouldPinParametersPanel, + shouldShowGallery, + shouldShowParametersPanel, + } = useAppSelector((state: RootState) => state.ui); const { t } = useTranslation(); @@ -133,9 +159,7 @@ const InvokeTabs = () => { const tabPanels = useMemo( () => - enabledTabs.map((tab) => ( - {tab.workarea} - )), + enabledTabs.map((tab) => {tab.content}), [enabledTabs] ); @@ -165,3 +189,17 @@ const InvokeTabs = () => { }; export default memo(InvokeTabs); + +// +// +// +// +// +// +// +// +// +// +// +// +// ; diff --git a/invokeai/frontend/web/src/features/ui/components/common/OverlayScrollable.tsx b/invokeai/frontend/web/src/features/ui/components/common/OverlayScrollable.tsx new file mode 100644 index 0000000000..af4f65f011 --- /dev/null +++ b/invokeai/frontend/web/src/features/ui/components/common/OverlayScrollable.tsx @@ -0,0 +1,24 @@ +import { OverlayScrollbarsComponent } from 'overlayscrollbars-react'; +import { PropsWithChildren, memo } from 'react'; + +const OverlayScrollable = (props: PropsWithChildren) => { + return ( + + {props.children} + + ); +}; + +export default memo(OverlayScrollable); diff --git a/invokeai/frontend/web/src/features/ui/components/common/ResizableDrawer/ResizableDrawer.tsx b/invokeai/frontend/web/src/features/ui/components/common/ResizableDrawer/ResizableDrawer.tsx index cabe58ccf2..d9fd765656 100644 --- a/invokeai/frontend/web/src/features/ui/components/common/ResizableDrawer/ResizableDrawer.tsx +++ b/invokeai/frontend/web/src/features/ui/components/common/ResizableDrawer/ResizableDrawer.tsx @@ -26,7 +26,6 @@ import { type ResizableDrawerProps = ResizableProps & { children: ReactNode; isResizable: boolean; - isPinned: boolean; isOpen: boolean; onClose: () => void; direction?: SlideDirection; @@ -51,7 +50,6 @@ const ChakraResizeable = chakra(Resizable, { const ResizableDrawer = ({ direction = 'left', isResizable, - isPinned, isOpen, onClose, children, @@ -95,7 +93,7 @@ const ResizableDrawer = ({ handler: () => { onClose(); }, - enabled: isOpen && !isPinned, + enabled: isOpen, }); const handleEnables = useMemo( @@ -107,30 +105,33 @@ const ResizableDrawer = ({ () => getMinMaxDimensions({ direction, - minWidth: isResizable - ? parseAndPadSize(minWidth, 18) - : parseAndPadSize(minWidth), - maxWidth: isResizable - ? parseAndPadSize(maxWidth, 18) - : parseAndPadSize(maxWidth), - minHeight: isResizable - ? parseAndPadSize(minHeight, 18) - : parseAndPadSize(minHeight), - maxHeight: isResizable - ? parseAndPadSize(maxHeight, 18) - : parseAndPadSize(maxHeight), + // minWidth: isResizable + // ? parseAndPadSize(minWidth, 18) + // : parseAndPadSize(minWidth), + // maxWidth: isResizable + // ? parseAndPadSize(maxWidth, 18) + // : parseAndPadSize(maxWidth), + // minHeight: isResizable + // ? parseAndPadSize(minHeight, 18) + // : parseAndPadSize(minHeight), + // maxHeight: isResizable + // ? parseAndPadSize(maxHeight, 18) + // : parseAndPadSize(maxHeight), + minWidth, + maxWidth, + minHeight, + maxHeight, }), - [minWidth, maxWidth, minHeight, maxHeight, direction, isResizable] + [minWidth, maxWidth, minHeight, maxHeight, direction] ); const { containerStyles, handleStyles } = useMemo( () => getStyles({ - isPinned, isResizable, direction, }), - [isPinned, isResizable, direction] + [isResizable, direction] ); const slideDirection = useMemo( @@ -140,34 +141,37 @@ const ResizableDrawer = ({ useEffect(() => { if (['left', 'right'].includes(direction)) { - setHeight(isPinned ? '100%' : '100vh'); + setHeight('100vh'); + // setHeight(isPinned ? '100%' : '100vh'); } if (['top', 'bottom'].includes(direction)) { - setWidth(isPinned ? '100%' : '100vw'); + setWidth('100vw'); + // setWidth(isPinned ? '100%' : '100vw'); } - }, [isPinned, direction]); + }, [direction]); return ( { + const { + shouldPinGallery, + shouldShowGallery, + shouldPinParametersPanel, + shouldShowParametersPanel, + } = ui; + + return { + shouldPinGallery, + shouldShowGallery, + shouldPinParametersPanel, + shouldShowParametersPanel, + }; +}); + +const CreateTab = () => { + const dispatch = useAppDispatch(); + const { + shouldPinGallery, + shouldShowGallery, + shouldPinParametersPanel, + shouldShowParametersPanel, + } = useAppSelector(selector); + + return ( + + {shouldPinParametersPanel && shouldShowParametersPanel && ( + <> + + + + + + + )} + {shouldPinParametersPanel && shouldShowParametersPanel && ( + <> + + + + + + )} + { + dispatch(requestCanvasRescale()); + }} + > + + + {shouldPinGallery && shouldShowGallery && ( + <> + + + + + + )} + + ); +}; + +export default memo(CreateTab); diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/Generate/GenerateContent.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/Create/GenerateContent.tsx similarity index 88% rename from invokeai/frontend/web/src/features/ui/components/tabs/Generate/GenerateContent.tsx rename to invokeai/frontend/web/src/features/ui/components/tabs/Create/GenerateContent.tsx index de7b738956..cd0c6a6142 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/Generate/GenerateContent.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/Create/GenerateContent.tsx @@ -2,7 +2,7 @@ import { Box, Flex } from '@chakra-ui/react'; import CurrentImageDisplay from 'features/gallery/components/CurrentImageDisplay'; import ProgressImagePreview from 'features/parameters/components/ProgressImagePreview'; -const GenerateContent = () => { +const CreateTabContent = () => { return ( { ); }; -export default GenerateContent; +export default CreateTabContent; diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/Generate/GenerateParameters.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/Create/GenerateParameters.tsx similarity index 80% rename from invokeai/frontend/web/src/features/ui/components/tabs/Generate/GenerateParameters.tsx rename to invokeai/frontend/web/src/features/ui/components/tabs/Create/GenerateParameters.tsx index 5b56fa5b0c..b03594eec6 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/Generate/GenerateParameters.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/Create/GenerateParameters.tsx @@ -30,9 +30,15 @@ import ProcessButtons from 'features/parameters/components/ProcessButtons/Proces import NegativePromptInput from 'features/parameters/components/PromptInput/NegativePromptInput'; import PromptInput from 'features/parameters/components/PromptInput/PromptInput'; import { findIndex } from 'lodash-es'; -import { memo, useMemo, useState } from 'react'; +import { + OverlayScrollbarsComponent, + useOverlayScrollbars, +} from 'overlayscrollbars-react'; +import { memo, useMemo, useState, useRef, useEffect } from 'react'; import { useTranslation } from 'react-i18next'; import { PARAMETERS_PANEL_WIDTH } from 'theme/util/constants'; +import OverlayScrollable from '../../common/OverlayScrollable'; +import AnimatedImageToImagePanel from 'features/parameters/components/AnimatedImageToImagePanel'; const GenerateParameters = () => { const { t } = useTranslation(); @@ -83,25 +89,35 @@ const GenerateParameters = () => { ); return ( - - - - + - + + + + + + + + - - - + ); }; diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/Generate/GenerateWorkspace.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/Create/GenerateWorkspace.tsx similarity index 93% rename from invokeai/frontend/web/src/features/ui/components/tabs/Generate/GenerateWorkspace.tsx rename to invokeai/frontend/web/src/features/ui/components/tabs/Create/GenerateWorkspace.tsx index df201af6ac..ebbf5cb5be 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/Generate/GenerateWorkspace.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/Create/GenerateWorkspace.tsx @@ -1,7 +1,7 @@ import { Box, Flex } from '@chakra-ui/react'; import { useAppSelector } from 'app/store/storeHooks'; import { memo } from 'react'; -import GenerateContent from './GenerateContent'; +import CreateTabContent from './GenerateContent'; import GenerateParameters from './GenerateParameters'; import PinParametersPanelButton from '../../PinParametersPanelButton'; import { RootState } from 'app/store/store'; @@ -14,6 +14,8 @@ const GenerateWorkspace = () => { (state: RootState) => state.ui.shouldPinParametersPanel ); + return ; + return ( { )} - + ); }; diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/Nodes/NodesTab.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/Nodes/NodesTab.tsx new file mode 100644 index 0000000000..d754cca45e --- /dev/null +++ b/invokeai/frontend/web/src/features/ui/components/tabs/Nodes/NodesTab.tsx @@ -0,0 +1,64 @@ +import { TabPanel } from '@chakra-ui/react'; +import { memo } from 'react'; +import { Panel, PanelGroup, PanelResizeHandle } from 'react-resizable-panels'; +import PinParametersPanelButton from '../../PinParametersPanelButton'; +import ImageGalleryContent from 'features/gallery/components/ImageGalleryContent'; +import { createSelector } from '@reduxjs/toolkit'; +import { uiSelector } from 'features/ui/store/uiSelectors'; +import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import { requestCanvasRescale } from 'features/canvas/store/thunks/requestCanvasScale'; +import ResizeHandle from '../ResizeHandle'; +import NodeEditor from 'features/nodes/components/NodeEditor'; + +const selector = createSelector(uiSelector, (ui) => { + const { + shouldPinGallery, + shouldShowGallery, + shouldPinParametersPanel, + shouldShowParametersPanel, + } = ui; + + return { + shouldPinGallery, + shouldShowGallery, + shouldPinParametersPanel, + shouldShowParametersPanel, + }; +}); + +const NodesTab = () => { + const dispatch = useAppDispatch(); + const { + shouldPinGallery, + shouldShowGallery, + shouldPinParametersPanel, + shouldShowParametersPanel, + } = useAppSelector(selector); + + return ( + + { + dispatch(requestCanvasRescale()); + }} + > + + + {shouldPinGallery && shouldShowGallery && ( + <> + + + + + + )} + + ); +}; + +export default memo(NodesTab); diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ResizeHandle.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/ResizeHandle.tsx new file mode 100644 index 0000000000..02f2486fbe --- /dev/null +++ b/invokeai/frontend/web/src/features/ui/components/tabs/ResizeHandle.tsx @@ -0,0 +1,17 @@ +import { Box, Flex } from '@chakra-ui/react'; +import { memo } from 'react'; +import { PanelResizeHandle } from 'react-resizable-panels'; + +const ResizeHandle = () => { + return ( + + + + + + ); +}; + +export default memo(ResizeHandle); diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasParameters.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasParameters.tsx index d0b009a6f5..5e64f72cc4 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasParameters.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasParameters.tsx @@ -17,6 +17,7 @@ import ProcessButtons from 'features/parameters/components/ProcessButtons/Proces import NegativePromptInput from 'features/parameters/components/PromptInput/NegativePromptInput'; import PromptInput from 'features/parameters/components/PromptInput/PromptInput'; import { useTranslation } from 'react-i18next'; +import OverlayScrollable from '../../common/OverlayScrollable'; export default function UnifiedCanvasParameters() { const { t } = useTranslation(); @@ -74,11 +75,21 @@ export default function UnifiedCanvasParameters() { }; return ( - - - - - - + + + + + + + + ); } diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasTab.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasTab.tsx new file mode 100644 index 0000000000..382bfd8105 --- /dev/null +++ b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasTab.tsx @@ -0,0 +1,89 @@ +import { TabPanel } from '@chakra-ui/react'; +import { memo } from 'react'; +import { Panel, PanelGroup, PanelResizeHandle } from 'react-resizable-panels'; +import PinParametersPanelButton from '../../PinParametersPanelButton'; +import ImageGalleryContent from 'features/gallery/components/ImageGalleryContent'; +import { createSelector } from '@reduxjs/toolkit'; +import { uiSelector } from 'features/ui/store/uiSelectors'; +import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import { requestCanvasRescale } from 'features/canvas/store/thunks/requestCanvasScale'; +import UnifiedCanvasContent from './UnifiedCanvasContent'; +import ResizeHandle from '../ResizeHandle'; +import UnifiedCanvasParameters from './UnifiedCanvasParameters'; +import UnifiedCanvasContentBeta from './UnifiedCanvasBeta/UnifiedCanvasContentBeta'; + +const selector = createSelector(uiSelector, (ui) => { + const { + shouldPinGallery, + shouldShowGallery, + shouldPinParametersPanel, + shouldShowParametersPanel, + shouldUseCanvasBetaLayout, + } = ui; + + return { + shouldPinGallery, + shouldShowGallery, + shouldPinParametersPanel, + shouldShowParametersPanel, + shouldUseCanvasBetaLayout, + }; +}); + +const UnifiedCanvasTab = () => { + const dispatch = useAppDispatch(); + const { + shouldPinGallery, + shouldShowGallery, + shouldPinParametersPanel, + shouldShowParametersPanel, + shouldUseCanvasBetaLayout, + } = useAppSelector(selector); + + return ( + + {shouldPinParametersPanel && shouldShowParametersPanel && ( + <> + + + + + + + )} + { + dispatch(requestCanvasRescale()); + }} + > + {shouldUseCanvasBetaLayout ? ( + + ) : ( + + )} + + {shouldPinGallery && shouldShowGallery && ( + <> + + + + + + )} + + ); +}; + +export default memo(UnifiedCanvasTab); diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasWorkarea.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasWorkarea.tsx index ee7f4d242d..53a7ca0d76 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasWorkarea.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasWorkarea.tsx @@ -18,6 +18,12 @@ const CanvasWorkspace = () => { (state: RootState) => state.ui.shouldUseCanvasBetaLayout ); + return shouldUseCanvasBetaLayout ? ( + + ) : ( + + ); + return ( { h="full" gap={4} > - {shouldPinParametersPanel ? ( + {/* {shouldPinParametersPanel ? ( @@ -38,7 +44,7 @@ const CanvasWorkspace = () => { - )} + )} */} {shouldUseCanvasBetaLayout ? ( ) : ( diff --git a/invokeai/frontend/web/src/features/ui/store/uiSlice.ts b/invokeai/frontend/web/src/features/ui/store/uiSlice.ts index 4a21a729d7..c5d3442f38 100644 --- a/invokeai/frontend/web/src/features/ui/store/uiSlice.ts +++ b/invokeai/frontend/web/src/features/ui/store/uiSlice.ts @@ -78,9 +78,15 @@ export const uiSlice = createSlice({ }, togglePinGalleryPanel: (state) => { state.shouldPinGallery = !state.shouldPinGallery; + if (!state.shouldPinGallery) { + state.shouldShowGallery = true; + } }, togglePinParametersPanel: (state) => { state.shouldPinParametersPanel = !state.shouldPinParametersPanel; + if (!state.shouldPinParametersPanel) { + state.shouldShowParametersPanel = true; + } }, toggleParametersPanel: (state) => { state.shouldShowParametersPanel = !state.shouldShowParametersPanel; diff --git a/invokeai/frontend/web/yarn.lock b/invokeai/frontend/web/yarn.lock index 1e4c30581a..90bc4055ac 100644 --- a/invokeai/frontend/web/yarn.lock +++ b/invokeai/frontend/web/yarn.lock @@ -5579,6 +5579,11 @@ react-remove-scroll@^2.5.5: use-callback-ref "^1.3.0" use-sidecar "^1.1.2" +react-resizable-panels@^0.0.42: + version "0.0.42" + resolved "https://registry.yarnpkg.com/react-resizable-panels/-/react-resizable-panels-0.0.42.tgz#e1a5d7fde7be4d18f32d0e021a0b4edb28b9edfe" + integrity sha512-nOaN9DeUTsmKjN3MFGaLd35kngGyO29SHRLdBRafYR1SV2F/LbWbpVUKVPwL2fBBTnQe2/rqOQwT4k+3cKeK+w== + react-rnd@^10.4.1: version "10.4.1" resolved "https://registry.yarnpkg.com/react-rnd/-/react-rnd-10.4.1.tgz#9e1c3f244895d7862ef03be98b2a620848c3fba1"