diff --git a/invokeai/frontend/web/.eslintrc.js b/invokeai/frontend/web/.eslintrc.js
index 489d3cfdff..b1a2b6a7e4 100644
--- a/invokeai/frontend/web/.eslintrc.js
+++ b/invokeai/frontend/web/.eslintrc.js
@@ -35,6 +35,7 @@ module.exports = {
{ varsIgnorePattern: '^_', argsIgnorePattern: '^_' },
],
'prettier/prettier': ['error', { endOfLine: 'auto' }],
+ '@typescript-eslint/ban-ts-comment': 'warn',
},
settings: {
react: {
diff --git a/invokeai/frontend/web/package.json b/invokeai/frontend/web/package.json
index d61c46a2d3..ba4371f86f 100644
--- a/invokeai/frontend/web/package.json
+++ b/invokeai/frontend/web/package.json
@@ -52,6 +52,8 @@
"i18next-http-backend": "^2.1.1",
"konva": "^8.4.2",
"lodash": "^4.17.21",
+ "overlayscrollbars": "^2.1.0",
+ "overlayscrollbars-react": "^0.5.0",
"re-resizable": "^6.9.9",
"react": "^18.2.0",
"react-colorful": "^5.6.1",
diff --git a/invokeai/frontend/web/src/app/App.tsx b/invokeai/frontend/web/src/app/App.tsx
index 3599577ca1..29c0a91613 100644
--- a/invokeai/frontend/web/src/app/App.tsx
+++ b/invokeai/frontend/web/src/app/App.tsx
@@ -9,7 +9,7 @@ import useToastWatcher from 'features/system/hooks/useToastWatcher';
import FloatingGalleryButton from 'features/ui/components/FloatingGalleryButton';
import FloatingParametersPanelButtons from 'features/ui/components/FloatingParametersPanelButtons';
-import { Box, Grid } from '@chakra-ui/react';
+import { Box, Grid, Portal } from '@chakra-ui/react';
import { APP_HEIGHT, APP_PADDING, APP_WIDTH } from 'theme/util/constants';
keepGUIAlive();
@@ -18,26 +18,32 @@ const App = () => {
useToastWatcher();
return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
);
};
diff --git a/invokeai/frontend/web/src/app/ThemeLocaleProvider.tsx b/invokeai/frontend/web/src/app/ThemeLocaleProvider.tsx
index 51fad5a431..a2ea3dac3a 100644
--- a/invokeai/frontend/web/src/app/ThemeLocaleProvider.tsx
+++ b/invokeai/frontend/web/src/app/ThemeLocaleProvider.tsx
@@ -9,6 +9,17 @@ import { greenTeaThemeColors } from 'theme/colors/greenTea';
import { invokeAIThemeColors } from 'theme/colors/invokeAI';
import { lightThemeColors } from 'theme/colors/lightTheme';
import { oceanBlueColors } from 'theme/colors/oceanBlue';
+import '@fontsource/inter/100.css';
+import '@fontsource/inter/200.css';
+import '@fontsource/inter/300.css';
+import '@fontsource/inter/400.css';
+import '@fontsource/inter/500.css';
+import '@fontsource/inter/600.css';
+import '@fontsource/inter/700.css';
+import '@fontsource/inter/800.css';
+import '@fontsource/inter/900.css';
+import 'overlayscrollbars/overlayscrollbars.css';
+import 'theme/overlayscrollbar.css';
type ThemeLocaleProviderProps = {
children: ReactNode;
diff --git a/invokeai/frontend/web/src/common/components/GuidePopover.tsx b/invokeai/frontend/web/src/common/components/GuidePopover.tsx
index ee376901e0..4c53e6e8a1 100644
--- a/invokeai/frontend/web/src/common/components/GuidePopover.tsx
+++ b/invokeai/frontend/web/src/common/components/GuidePopover.tsx
@@ -30,7 +30,7 @@ const GuidePopover = ({ children, feature }: GuideProps) => {
if (!shouldDisplayGuides) return null;
return (
-
+
{children}
diff --git a/invokeai/frontend/web/src/features/ui/components/ImageToImage/ImageToImageSettings.tsx b/invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/ImageToImage/ImageToImageSettings.tsx
similarity index 100%
rename from invokeai/frontend/web/src/features/ui/components/ImageToImage/ImageToImageSettings.tsx
rename to invokeai/frontend/web/src/features/parameters/components/AdvancedParameters/ImageToImage/ImageToImageSettings.tsx
diff --git a/invokeai/frontend/web/src/features/parameters/components/ParametersAccordion.tsx b/invokeai/frontend/web/src/features/parameters/components/ParametersAccordion.tsx
index 019fb02f90..76277867de 100644
--- a/invokeai/frontend/web/src/features/parameters/components/ParametersAccordion.tsx
+++ b/invokeai/frontend/web/src/features/parameters/components/ParametersAccordion.tsx
@@ -59,6 +59,11 @@ const ParametersAccordion = (props: ParametersAccordionsType) => {
allowMultiple
reduceMotion
onChange={handleChangeAccordionState}
+ sx={{
+ display: 'flex',
+ flexDirection: 'column',
+ gap: 2,
+ }}
>
{renderAccordions()}
diff --git a/invokeai/frontend/web/src/features/ui/components/FloatingParametersPanelButtons.tsx b/invokeai/frontend/web/src/features/ui/components/FloatingParametersPanelButtons.tsx
index 0466131156..2749da2890 100644
--- a/invokeai/frontend/web/src/features/ui/components/FloatingParametersPanelButtons.tsx
+++ b/invokeai/frontend/web/src/features/ui/components/FloatingParametersPanelButtons.tsx
@@ -28,7 +28,6 @@ export const floatingSelector = createSelector(
const {
shouldPinParametersPanel,
shouldShowParametersPanel,
- shouldHoldParametersPanelOpen,
shouldUseCanvasBetaLayout,
} = ui;
@@ -40,10 +39,7 @@ export const floatingSelector = createSelector(
const shouldShowParametersPanelButton =
!canvasBetaLayoutCheck &&
- !(
- shouldShowParametersPanel ||
- (shouldHoldParametersPanelOpen && !shouldPinParametersPanel)
- ) &&
+ !shouldPinParametersPanel &&
['txt2img', 'img2img', 'unifiedCanvas'].includes(activeTabName);
const shouldShowGalleryButton =
@@ -51,8 +47,7 @@ export const floatingSelector = createSelector(
['txt2img', 'img2img', 'unifiedCanvas'].includes(activeTabName);
const shouldShowProcessButtons =
- !canvasBetaLayoutCheck &&
- (!shouldPinParametersPanel || !shouldShowParametersPanel);
+ !canvasBetaLayoutCheck && !shouldPinParametersPanel;
return {
shouldPinParametersPanel,
diff --git a/invokeai/frontend/web/src/features/ui/components/ImageToImage/InitialImageOverlay.tsx b/invokeai/frontend/web/src/features/ui/components/ImageToImage/InitialImageOverlay.tsx
deleted file mode 100644
index 99b3ba4c32..0000000000
--- a/invokeai/frontend/web/src/features/ui/components/ImageToImage/InitialImageOverlay.tsx
+++ /dev/null
@@ -1,18 +0,0 @@
-import { Image } from '@chakra-ui/react';
-import { RootState } from 'app/store';
-import { useAppSelector } from 'app/storeHooks';
-
-export default function InitialImageOverlay() {
- const initialImage = useAppSelector(
- (state: RootState) => state.generation.initialImage
- );
-
- return initialImage ? (
-
- ) : null;
-}
diff --git a/invokeai/frontend/web/src/features/ui/components/ImageToImage/index.tsx b/invokeai/frontend/web/src/features/ui/components/ImageToImage/index.tsx
deleted file mode 100644
index e1c38ad75f..0000000000
--- a/invokeai/frontend/web/src/features/ui/components/ImageToImage/index.tsx
+++ /dev/null
@@ -1,11 +0,0 @@
-import InvokeWorkarea from 'features/ui/components/InvokeWorkarea';
-import ImageToImageDisplay from './ImageToImageDisplay';
-import ImageToImagePanel from './ImageToImagePanel';
-
-export default function ImageToImageWorkarea() {
- return (
- }>
-
-
- );
-}
diff --git a/invokeai/frontend/web/src/features/ui/components/InvokeParametersPanel.css b/invokeai/frontend/web/src/features/ui/components/InvokeParametersPanel.css
deleted file mode 100644
index f43861edba..0000000000
--- a/invokeai/frontend/web/src/features/ui/components/InvokeParametersPanel.css
+++ /dev/null
@@ -1,35 +0,0 @@
-.ltr-parameters-panel-transition-enter {
- transform: translateX(-150%);
-}
-
-.ltr-parameters-panel-transition-enter-active {
- transform: translateX(0);
- transition: all 120ms ease-out;
-}
-
-.ltr-parameters-panel-transition-exit {
- transform: translateX(0);
-}
-
-.ltr-parameters-panel-transition-exit-active {
- transform: translateX(-150%);
- transition: all 120ms ease-out;
-}
-
-.rtl-parameters-panel-transition-enter {
- transform: translateX(150%);
-}
-
-.rtl-parameters-panel-transition-enter-active {
- transform: translateX(0);
- transition: all 120ms ease-out;
-}
-
-.rtl-parameters-panel-transition-exit {
- transform: translateX(0);
-}
-
-.rtl-parameters-panel-transition-exit-active {
- transform: translateX(150%);
- transition: all 120ms ease-out;
-}
diff --git a/invokeai/frontend/web/src/features/ui/components/InvokeParametersPanel.tsx b/invokeai/frontend/web/src/features/ui/components/InvokeParametersPanel.tsx
deleted file mode 100644
index e7e610df6c..0000000000
--- a/invokeai/frontend/web/src/features/ui/components/InvokeParametersPanel.tsx
+++ /dev/null
@@ -1,249 +0,0 @@
-import { Box, Flex, Tooltip, Icon, useTheme } from '@chakra-ui/react';
-import { createSelector } from '@reduxjs/toolkit';
-import { useAppDispatch, useAppSelector } from 'app/storeHooks';
-import {
- setShouldHoldParametersPanelOpen,
- setShouldPinParametersPanel,
- setShouldShowParametersPanel,
-} from 'features/ui/store/uiSlice';
-
-import React, { ReactNode, useCallback, useEffect, useRef } from 'react';
-import { useHotkeys } from 'react-hotkeys-hook';
-import { BsPinAngle, BsPinAngleFill } from 'react-icons/bs';
-import { CSSTransition } from 'react-transition-group';
-
-import { setDoesCanvasNeedScaling } from 'features/canvas/store/canvasSlice';
-import { setParametersPanelScrollPosition } from 'features/ui/store/uiSlice';
-
-import { isEqual } from 'lodash';
-import { uiSelector } from '../store/uiSelectors';
-import { useTranslation } from 'react-i18next';
-import {
- APP_CONTENT_HEIGHT,
- OPTIONS_BAR_MAX_WIDTH,
- PROGRESS_BAR_THICKNESS,
-} from 'theme/util/constants';
-import InvokeAILogoComponent from 'features/system/components/InvokeAILogoComponent';
-
-import './InvokeParametersPanel.css';
-import { no_scrollbar } from 'theme/components/scrollbar';
-
-type Props = { children: ReactNode };
-
-const optionsPanelSelector = createSelector(
- uiSelector,
- (ui) => {
- const {
- shouldShowParametersPanel,
- shouldHoldParametersPanelOpen,
- shouldPinParametersPanel,
- parametersPanelScrollPosition,
- } = ui;
-
- return {
- shouldShowParametersPanel,
- shouldHoldParametersPanelOpen,
- shouldPinParametersPanel,
- parametersPanelScrollPosition,
- };
- },
- {
- memoizeOptions: {
- resultEqualityCheck: isEqual,
- },
- }
-);
-
-const InvokeOptionsPanel = (props: Props) => {
- const dispatch = useAppDispatch();
- const { direction } = useTheme();
-
- const {
- shouldShowParametersPanel,
- shouldHoldParametersPanelOpen,
- shouldPinParametersPanel,
- } = useAppSelector(optionsPanelSelector);
-
- const optionsPanelRef = useRef(null);
- const optionsPanelContainerRef = useRef(null);
-
- const timeoutIdRef = useRef(null);
-
- const { children } = props;
-
- const { t } = useTranslation();
-
- // Hotkeys
- useHotkeys(
- 'o',
- () => {
- dispatch(setShouldShowParametersPanel(!shouldShowParametersPanel));
- shouldPinParametersPanel &&
- setTimeout(() => dispatch(setDoesCanvasNeedScaling(true)), 400);
- },
- [shouldShowParametersPanel, shouldPinParametersPanel]
- );
-
- useHotkeys(
- 'esc',
- () => {
- dispatch(setShouldShowParametersPanel(false));
- },
- {
- enabled: () => !shouldPinParametersPanel,
- preventDefault: true,
- },
- [shouldPinParametersPanel]
- );
-
- useHotkeys(
- 'shift+o',
- () => {
- handleClickPinOptionsPanel();
- dispatch(setDoesCanvasNeedScaling(true));
- },
- [shouldPinParametersPanel]
- );
-
- const handleCloseOptionsPanel = useCallback(() => {
- if (shouldPinParametersPanel) return;
- dispatch(
- setParametersPanelScrollPosition(
- optionsPanelContainerRef.current
- ? optionsPanelContainerRef.current.scrollTop
- : 0
- )
- );
- dispatch(setShouldShowParametersPanel(false));
- dispatch(setShouldHoldParametersPanelOpen(false));
- }, [dispatch, shouldPinParametersPanel]);
-
- const setCloseOptionsPanelTimer = () => {
- timeoutIdRef.current = window.setTimeout(
- () => handleCloseOptionsPanel(),
- 500
- );
- };
-
- const cancelCloseOptionsPanelTimer = () => {
- timeoutIdRef.current && window.clearTimeout(timeoutIdRef.current);
- };
-
- const handleClickPinOptionsPanel = () => {
- dispatch(setShouldPinParametersPanel(!shouldPinParametersPanel));
- dispatch(setDoesCanvasNeedScaling(true));
- };
-
- useEffect(() => {
- function handleClickOutside(e: MouseEvent) {
- if (
- optionsPanelRef.current &&
- !optionsPanelRef.current.contains(e.target as Node)
- ) {
- handleCloseOptionsPanel();
- }
- }
- document.addEventListener('mousedown', handleClickOutside);
- return () => {
- document.removeEventListener('mousedown', handleClickOutside);
- };
- }, [handleCloseOptionsPanel]);
-
- return (
-
-
-
- ) => {
- if (e.target !== optionsPanelContainerRef.current) {
- cancelCloseOptionsPanelTimer();
- } else {
- !shouldPinParametersPanel && setCloseOptionsPanelTimer();
- }
- }}
- sx={{
- display: 'flex',
- flexDirection: 'column',
- rowGap: 2,
- height: '100%',
- }}
- >
-
-
-
-
-
- {!shouldPinParametersPanel && (
-
-
-
- )}
- {children}
-
-
-
-
- );
-};
-
-export default InvokeOptionsPanel;
diff --git a/invokeai/frontend/web/src/features/ui/components/InvokeTabs.tsx b/invokeai/frontend/web/src/features/ui/components/InvokeTabs.tsx
index 740c71edfd..b4d27d484e 100644
--- a/invokeai/frontend/web/src/features/ui/components/InvokeTabs.tsx
+++ b/invokeai/frontend/web/src/features/ui/components/InvokeTabs.tsx
@@ -36,9 +36,9 @@ import {
} from 'react-icons/md';
import { activeTabIndexSelector } from '../store/uiSelectors';
import { floatingSelector } from './FloatingParametersPanelButtons';
-import ImageToImageWorkarea from './ImageToImage';
-import TextToImageWorkarea from './TextToImage';
-import UnifiedCanvasWorkarea from './UnifiedCanvas/UnifiedCanvasWorkarea';
+import ImageToImageWorkarea from 'features/ui/components/tabs/ImageToImage/ImageToImageWorkarea';
+import TextToImageWorkarea from 'features/ui/components/tabs/TextToImage/TextToImageWorkarea';
+import UnifiedCanvasWorkarea from 'features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasWorkarea';
export interface InvokeTabInfo {
title: ReactElement;
diff --git a/invokeai/frontend/web/src/features/ui/components/InvokeWorkarea.tsx b/invokeai/frontend/web/src/features/ui/components/InvokeWorkarea.tsx
deleted file mode 100644
index 0210d249cf..0000000000
--- a/invokeai/frontend/web/src/features/ui/components/InvokeWorkarea.tsx
+++ /dev/null
@@ -1,71 +0,0 @@
-import { Box, BoxProps, Flex } from '@chakra-ui/react';
-import { createSelector } from '@reduxjs/toolkit';
-import { useAppDispatch, useAppSelector } from 'app/storeHooks';
-import ImageGallery from 'features/gallery/components/ImageGallery';
-import { setInitialImage } from 'features/parameters/store/generationSlice';
-import {
- activeTabNameSelector,
- uiSelector,
-} from 'features/ui/store/uiSelectors';
-import { DragEvent, ReactNode } from 'react';
-
-import { setInitialCanvasImage } from 'features/canvas/store/canvasSlice';
-import useGetImageByUuid from 'features/gallery/hooks/useGetImageByUuid';
-import { lightboxSelector } from 'features/lightbox/store/lightboxSelectors';
-import { isEqual } from 'lodash';
-
-const workareaSelector = createSelector(
- [uiSelector, lightboxSelector, activeTabNameSelector],
- (ui, lightbox, activeTabName) => {
- const { shouldPinParametersPanel } = ui;
- const { isLightboxOpen } = lightbox;
- return {
- shouldPinParametersPanel,
- isLightboxOpen,
- activeTabName,
- };
- },
- {
- memoizeOptions: {
- resultEqualityCheck: isEqual,
- },
- }
-);
-
-type InvokeWorkareaProps = BoxProps & {
- optionsPanel: ReactNode;
- children: ReactNode;
-};
-
-const InvokeWorkarea = (props: InvokeWorkareaProps) => {
- const dispatch = useAppDispatch();
- const { optionsPanel, children, ...rest } = props;
- const { activeTabName, isLightboxOpen } = useAppSelector(workareaSelector);
-
- const getImageByUuid = useGetImageByUuid();
-
- const handleDrop = (e: DragEvent) => {
- const uuid = e.dataTransfer.getData('invokeai/imageUuid');
- const image = getImageByUuid(uuid);
- if (!image) return;
- if (activeTabName === 'img2img') {
- dispatch(setInitialImage(image));
- } else if (activeTabName === 'unifiedCanvas') {
- dispatch(setInitialCanvasImage(image));
- }
- };
-
- return (
-
-
- {optionsPanel}
-
- {children}
-
- {!isLightboxOpen && }
-
-
- );
-};
-
-export default InvokeWorkarea;
diff --git a/invokeai/frontend/web/src/features/ui/components/TextToImage/index.tsx b/invokeai/frontend/web/src/features/ui/components/TextToImage/index.tsx
deleted file mode 100644
index fccd862160..0000000000
--- a/invokeai/frontend/web/src/features/ui/components/TextToImage/index.tsx
+++ /dev/null
@@ -1,11 +0,0 @@
-import InvokeWorkarea from 'features/ui/components/InvokeWorkarea';
-import TextToImageDisplay from './TextToImageDisplay';
-import TextToImagePanel from './TextToImagePanel';
-
-export default function TextToImageWorkarea() {
- return (
- }>
-
-
- );
-}
diff --git a/invokeai/frontend/web/src/features/ui/components/UnifiedCanvas/UnifiedCanvasWorkarea.tsx b/invokeai/frontend/web/src/features/ui/components/UnifiedCanvas/UnifiedCanvasWorkarea.tsx
deleted file mode 100644
index b35504a2ac..0000000000
--- a/invokeai/frontend/web/src/features/ui/components/UnifiedCanvas/UnifiedCanvasWorkarea.tsx
+++ /dev/null
@@ -1,21 +0,0 @@
-import { RootState } from 'app/store';
-import { useAppSelector } from 'app/storeHooks';
-import InvokeWorkarea from 'features/ui/components/InvokeWorkarea';
-import UnifiedCanvasDisplayBeta from './UnifiedCanvasBeta/UnifiedCanvasDisplayBeta';
-import UnifiedCanvasDisplay from './UnifiedCanvasDisplay';
-import UnifiedCanvasPanel from './UnifiedCanvasPanel';
-
-export default function UnifiedCanvasWorkarea() {
- const shouldUseCanvasBetaLayout = useAppSelector(
- (state: RootState) => state.ui.shouldUseCanvasBetaLayout
- );
- return (
- }>
- {shouldUseCanvasBetaLayout ? (
-
- ) : (
-
- )}
-
- );
-}
diff --git a/invokeai/frontend/web/src/features/ui/components/common/InvokeWorkarea.tsx b/invokeai/frontend/web/src/features/ui/components/common/InvokeWorkarea.tsx
new file mode 100644
index 0000000000..53f4877636
--- /dev/null
+++ b/invokeai/frontend/web/src/features/ui/components/common/InvokeWorkarea.tsx
@@ -0,0 +1,147 @@
+import { Box, BoxProps, Flex } from '@chakra-ui/react';
+import { createSelector } from '@reduxjs/toolkit';
+import { useAppDispatch, useAppSelector } from 'app/storeHooks';
+import ImageGallery from 'features/gallery/components/ImageGallery';
+import { setInitialImage } from 'features/parameters/store/generationSlice';
+import {
+ activeTabNameSelector,
+ uiSelector,
+} from 'features/ui/store/uiSelectors';
+import { DragEvent, ReactNode } from 'react';
+
+import {
+ setDoesCanvasNeedScaling,
+ setInitialCanvasImage,
+} from 'features/canvas/store/canvasSlice';
+import useGetImageByUuid from 'features/gallery/hooks/useGetImageByUuid';
+import { lightboxSelector } from 'features/lightbox/store/lightboxSelectors';
+import { isEqual } from 'lodash';
+import {
+ APP_CONTENT_HEIGHT,
+ PARAMETERS_PANEL_WIDTH,
+} from 'theme/util/constants';
+import ResizableDrawer from 'features/ui/components/common/ResizableDrawer/ResizableDrawer';
+import {
+ setShouldPinParametersPanel,
+ setShouldShowParametersPanel,
+} from 'features/ui/store/uiSlice';
+import { useHotkeys } from 'react-hotkeys-hook';
+import InvokeAILogoComponent from 'features/system/components/InvokeAILogoComponent';
+
+const workareaSelector = createSelector(
+ [uiSelector, lightboxSelector, activeTabNameSelector],
+ (ui, lightbox, activeTabName) => {
+ const { shouldPinParametersPanel } = ui;
+ const { isLightboxOpen } = lightbox;
+ return {
+ shouldPinParametersPanel,
+ isLightboxOpen,
+ activeTabName,
+ };
+ },
+ {
+ memoizeOptions: {
+ resultEqualityCheck: isEqual,
+ },
+ }
+);
+
+type InvokeWorkareaProps = BoxProps & {
+ parametersPanel: ReactNode;
+ children: ReactNode;
+};
+
+const InvokeWorkarea = (props: InvokeWorkareaProps) => {
+ const { parametersPanel, children, ...rest } = props;
+ const dispatch = useAppDispatch();
+ const { activeTabName, isLightboxOpen } = useAppSelector(workareaSelector);
+ const { shouldPinParametersPanel, shouldShowParametersPanel } =
+ useAppSelector(uiSelector);
+
+ const getImageByUuid = useGetImageByUuid();
+
+ const handleDrop = (e: DragEvent) => {
+ const uuid = e.dataTransfer.getData('invokeai/imageUuid');
+ const image = getImageByUuid(uuid);
+ if (!image) return;
+ if (activeTabName === 'img2img') {
+ dispatch(setInitialImage(image));
+ } else if (activeTabName === 'unifiedCanvas') {
+ dispatch(setInitialCanvasImage(image));
+ }
+ };
+
+ const closeParametersPanel = () => {
+ dispatch(setShouldShowParametersPanel(false));
+ };
+
+ useHotkeys(
+ 'o',
+ () => {
+ dispatch(setShouldShowParametersPanel(!shouldShowParametersPanel));
+ shouldPinParametersPanel &&
+ setTimeout(() => dispatch(setDoesCanvasNeedScaling(true)), 400);
+ },
+ [shouldShowParametersPanel, shouldPinParametersPanel]
+ );
+
+ useHotkeys(
+ 'esc',
+ () => {
+ dispatch(setShouldShowParametersPanel(false));
+ },
+ {
+ enabled: () => !shouldPinParametersPanel,
+ preventDefault: true,
+ },
+ [shouldPinParametersPanel]
+ );
+
+ useHotkeys(
+ 'shift+o',
+ () => {
+ dispatch(setShouldPinParametersPanel(!shouldPinParametersPanel));
+ dispatch(setDoesCanvasNeedScaling(true));
+ },
+ [shouldPinParametersPanel]
+ );
+
+ return (
+
+
+
+ {!shouldPinParametersPanel && }
+ {parametersPanel}
+
+
+
+ {children}
+
+ {!isLightboxOpen && }
+
+ );
+};
+
+export default InvokeWorkarea;
diff --git a/invokeai/frontend/web/src/features/ui/components/common/PinParametersPanelButton.tsx b/invokeai/frontend/web/src/features/ui/components/common/PinParametersPanelButton.tsx
new file mode 100644
index 0000000000..d00b039f73
--- /dev/null
+++ b/invokeai/frontend/web/src/features/ui/components/common/PinParametersPanelButton.tsx
@@ -0,0 +1,44 @@
+import { Box, Icon, Tooltip } from '@chakra-ui/react';
+import { useAppDispatch, useAppSelector } from 'app/storeHooks';
+import IAIIconButton, {
+ IAIIconButtonProps,
+} from 'common/components/IAIIconButton';
+import { setDoesCanvasNeedScaling } from 'features/canvas/store/canvasSlice';
+import { useTranslation } from 'react-i18next';
+import { BsPinAngle, BsPinAngleFill } from 'react-icons/bs';
+import { setShouldPinParametersPanel } from '../../store/uiSlice';
+
+const PinParametersPanelButton = () => {
+ const dispatch = useAppDispatch();
+ const shouldPinParametersPanel = useAppSelector(
+ (state) => state.ui.shouldPinParametersPanel
+ );
+
+ const { t } = useTranslation();
+
+ const handleClickPinOptionsPanel = () => {
+ dispatch(setShouldPinParametersPanel(!shouldPinParametersPanel));
+ dispatch(setDoesCanvasNeedScaling(true));
+ };
+
+ return (
+
+ : }
+ variant="unstyled"
+ size="sm"
+ padding={2}
+ sx={{
+ position: 'absolute',
+ top: 1,
+ insetInlineEnd: 1,
+ }}
+ />
+
+ );
+};
+
+export default PinParametersPanelButton;
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
new file mode 100644
index 0000000000..df8fcf5a72
--- /dev/null
+++ b/invokeai/frontend/web/src/features/ui/components/common/ResizableDrawer/ResizableDrawer.tsx
@@ -0,0 +1,217 @@
+import {
+ Box,
+ chakra,
+ ChakraProps,
+ Slide,
+ useOutsideClick,
+ useTheme,
+ SlideDirection,
+} from '@chakra-ui/react';
+import {
+ Resizable,
+ ResizableProps,
+ ResizeCallback,
+ ResizeStartCallback,
+} from 're-resizable';
+import { ReactNode, useEffect, useMemo, useRef, useState } from 'react';
+import { LangDirection } from './types';
+import {
+ getDefaultSize,
+ getHandleEnables,
+ getHandleStyles,
+ getMinMaxDimensions,
+ getResizableStyles,
+} from './util';
+import Scrollable from '../Scrollable';
+
+type ResizableDrawerProps = ResizableProps & {
+ children: ReactNode;
+ isResizable: boolean;
+ isPinned: boolean;
+ isOpen: boolean;
+ onClose: () => void;
+ direction?: SlideDirection;
+ initialWidth?: string | number;
+ minWidth?: string | number;
+ maxWidth?: string | number;
+ initialHeight?: string | number;
+ minHeight?: string | number;
+ maxHeight?: string | number;
+ shouldAllowResize?: boolean;
+ onResizeStart?: ResizeStartCallback;
+ onResizeStop?: ResizeCallback;
+ onResize?: ResizeCallback;
+ handleWidth?: number;
+ handleInteractWidth?: string | number;
+ sx?: ChakraProps['sx'];
+ pinnedWidth: number;
+ pinnedHeight: string | number;
+};
+
+const ChakraResizeable = chakra(Resizable, {
+ shouldForwardProp: (prop) => !['sx'].includes(prop),
+});
+
+const ResizableDrawer = ({
+ direction = 'left',
+ isResizable,
+ isPinned,
+ isOpen,
+ onClose,
+ children,
+ initialWidth = undefined,
+ minWidth = undefined,
+ maxWidth = undefined,
+ initialHeight = undefined,
+ minHeight = undefined,
+ maxHeight = undefined,
+ shouldAllowResize,
+ onResizeStart,
+ onResizeStop,
+ onResize,
+ handleWidth = 5,
+ handleInteractWidth = '15px',
+ pinnedWidth,
+ pinnedHeight,
+ sx = {},
+}: ResizableDrawerProps) => {
+ const langDirection = useTheme().direction as LangDirection;
+
+ const outsideClickRef = useRef(null);
+
+ useOutsideClick({
+ ref: outsideClickRef,
+ handler: () => {
+ if (isPinned) {
+ return;
+ }
+
+ onClose();
+ },
+ });
+
+ const [width, setWidth] = useState(0);
+ const [height, setHeight] = useState(0);
+
+ const handleEnables = useMemo(
+ () =>
+ isResizable && shouldAllowResize
+ ? getHandleEnables({ direction, langDirection })
+ : {},
+ [isResizable, shouldAllowResize, langDirection, direction]
+ );
+
+ const handleStyles = useMemo(
+ () =>
+ getHandleStyles({
+ handleEnables,
+ handleStyle: {
+ width: handleInteractWidth,
+ },
+ }),
+ [handleEnables, handleInteractWidth]
+ );
+
+ const minMaxDimensions = useMemo(
+ () =>
+ getMinMaxDimensions({
+ direction,
+ minWidth,
+ maxWidth,
+ minHeight,
+ maxHeight,
+ }),
+ [minWidth, maxWidth, minHeight, maxHeight, direction]
+ );
+
+ const resizableStyles = useMemo(
+ () => getResizableStyles({ isPinned, direction, sx, handleWidth }),
+ [sx, handleWidth, direction, isPinned]
+ );
+
+ useEffect(() => {
+ const { width, height } = getDefaultSize({
+ initialWidth,
+ initialHeight,
+ direction,
+ });
+
+ setWidth(width);
+ setHeight(height);
+ }, [initialWidth, initialHeight, direction, langDirection]);
+
+ useEffect(() => {
+ if (['left', 'right'].includes(direction)) {
+ setHeight(isPinned ? '100%' : '100vh');
+ }
+ if (['top', 'bottom'].includes(direction)) {
+ setWidth(isPinned ? '100%' : '100vw');
+ }
+ }, [isPinned, direction]);
+
+ return (
+
+
+ {
+ onResizeStart && onResizeStart(event, direction, elementRef);
+ }}
+ onResize={(event, direction, elementRef, delta) => {
+ onResize && onResize(event, direction, elementRef, delta);
+ }}
+ onResizeStop={(event, direction, elementRef, delta) => {
+ event.stopPropagation();
+ event.stopImmediatePropagation();
+ event.preventDefault();
+ if (direction === 'left' || direction === 'right') {
+ setWidth(Number(width) + delta.width);
+ }
+ if (direction === 'top' || direction === 'bottom') {
+ setHeight(Number(height) + delta.height);
+ }
+ onResizeStop && onResizeStop(event, direction, elementRef, delta);
+ }}
+ >
+ {children}
+
+
+
+ );
+};
+
+export default ResizableDrawer;
diff --git a/invokeai/frontend/web/src/features/ui/components/common/ResizableDrawer/types.ts b/invokeai/frontend/web/src/features/ui/components/common/ResizableDrawer/types.ts
new file mode 100644
index 0000000000..5aa42ab7e5
--- /dev/null
+++ b/invokeai/frontend/web/src/features/ui/components/common/ResizableDrawer/types.ts
@@ -0,0 +1,2 @@
+export type Placement = 'top' | 'right' | 'bottom' | 'left';
+export type LangDirection = 'ltr' | 'rtl' | undefined;
diff --git a/invokeai/frontend/web/src/features/ui/components/common/ResizableDrawer/util.ts b/invokeai/frontend/web/src/features/ui/components/common/ResizableDrawer/util.ts
new file mode 100644
index 0000000000..61c5f1093f
--- /dev/null
+++ b/invokeai/frontend/web/src/features/ui/components/common/ResizableDrawer/util.ts
@@ -0,0 +1,211 @@
+import { ChakraProps, SlideDirection } from '@chakra-ui/react';
+import { AnimationProps } from 'framer-motion';
+import { Enable } from 're-resizable';
+import React from 'react';
+import { LangDirection } from './types';
+
+export type GetHandleEnablesOptions = {
+ direction: SlideDirection;
+ langDirection: LangDirection;
+};
+
+// Determine handles to enable, taking into account language direction
+export const getHandleEnables = ({
+ direction,
+ langDirection,
+}: GetHandleEnablesOptions) => {
+ const top = direction === 'bottom';
+
+ const right =
+ (langDirection !== 'rtl' && direction === 'left') ||
+ (langDirection === 'rtl' && direction === 'right');
+
+ const bottom = direction === 'top';
+
+ const left =
+ (langDirection !== 'rtl' && direction === 'right') ||
+ (langDirection === 'rtl' && direction === 'left');
+
+ return { top, right, bottom, left };
+};
+
+export type GetDefaultSizeOptions = {
+ initialWidth?: string | number;
+ initialHeight?: string | number;
+ direction: SlideDirection;
+};
+
+// Get default sizes based on direction and initial values
+export const getDefaultSize = ({
+ initialWidth,
+ initialHeight,
+ direction,
+}: GetDefaultSizeOptions) => {
+ const width =
+ initialWidth ?? (['left', 'right'].includes(direction) ? 500 : '100vw');
+
+ const height =
+ initialHeight ?? (['top', 'bottom'].includes(direction) ? 500 : '100vh');
+
+ return { width, height };
+};
+
+export type GetMinMaxDimensionsOptions = {
+ direction: SlideDirection;
+ minWidth?: string | number;
+ maxWidth?: string | number;
+ minHeight?: string | number;
+ maxHeight?: string | number;
+};
+
+// Get the min/max width/height based on direction and provided values
+export const getMinMaxDimensions = ({
+ direction,
+ minWidth,
+ maxWidth,
+ minHeight,
+ maxHeight,
+}: GetMinMaxDimensionsOptions) => {
+ const minW =
+ minWidth ?? (['left', 'right'].includes(direction) ? 10 : undefined);
+
+ const maxW =
+ maxWidth ?? (['left', 'right'].includes(direction) ? '95vw' : undefined);
+
+ const minH =
+ minHeight ?? (['top', 'bottom'].includes(direction) ? 10 : undefined);
+
+ const maxH =
+ maxHeight ?? (['top', 'bottom'].includes(direction) ? '95vh' : undefined);
+
+ return { minWidth: minW, maxWidth: maxW, minHeight: minH, maxHeight: maxH };
+};
+
+export type GetHandleStylesOptions = {
+ handleEnables: Enable;
+ handleStyle?: React.CSSProperties;
+};
+
+// Get handle styles, the enables already have language direction factored in so
+// that does not need to be handled here
+export const getHandleStyles = ({
+ handleEnables,
+ handleStyle,
+}: GetHandleStylesOptions) => {
+ if (!handleStyle) {
+ return {};
+ }
+
+ const top = handleEnables.top ? handleStyle : {};
+ const right = handleEnables.right ? handleStyle : {};
+ const bottom = handleEnables.bottom ? handleStyle : {};
+ const left = handleEnables.left ? handleStyle : {};
+
+ return {
+ top,
+ right,
+ bottom,
+ left,
+ };
+};
+
+export type GetAnimationsOptions = {
+ direction: SlideDirection;
+ langDirection: LangDirection;
+};
+
+// Get the framer-motion animation props, taking into account language direction
+export const getAnimations = ({
+ direction,
+ langDirection,
+}: GetAnimationsOptions): AnimationProps => {
+ const baseAnimation = {
+ initial: { opacity: 0 },
+ animate: { opacity: 1 },
+ exit: { opacity: 0 },
+ // chakra consumes the transition prop, which, for it, is a string.
+ // however we know the transition prop will make it to framer motion,
+ // which wants it as an object. cast as string to satisfy TS.
+ transition: { duration: 0.2, ease: 'easeInOut' },
+ };
+
+ const langDirectionFactor = langDirection === 'rtl' ? -1 : 1;
+
+ if (direction === 'top') {
+ return {
+ ...baseAnimation,
+ initial: { y: -999 },
+ animate: { y: 0 },
+ exit: { y: -999 },
+ };
+ }
+
+ if (direction === 'right') {
+ return {
+ ...baseAnimation,
+ initial: { x: 999 * langDirectionFactor },
+ animate: { x: 0 },
+ exit: { x: 999 * langDirectionFactor },
+ };
+ }
+
+ if (direction === 'bottom') {
+ return {
+ ...baseAnimation,
+ initial: { y: 999 },
+ animate: { y: 0 },
+ exit: { y: 999 },
+ };
+ }
+
+ if (direction === 'left') {
+ return {
+ ...baseAnimation,
+ initial: { x: -999 * langDirectionFactor },
+ animate: { x: 0 },
+ exit: { x: -999 * langDirectionFactor },
+ };
+ }
+
+ return {};
+};
+
+export type GetResizableStylesProps = {
+ sx: ChakraProps['sx'];
+ direction: SlideDirection;
+ handleWidth: number;
+ isPinned: boolean;
+};
+
+export const getResizableStyles = ({
+ isPinned, // TODO add borderRadius for pinned?
+ sx,
+ direction,
+ handleWidth,
+}: GetResizableStylesProps): ChakraProps['sx'] => {
+ if (isPinned) {
+ return sx;
+ }
+
+ if (direction === 'top') {
+ return {
+ borderBottomWidth: handleWidth,
+ ...sx,
+ };
+ }
+
+ if (direction === 'right') {
+ return { borderInlineStartWidth: handleWidth, ...sx };
+ }
+
+ if (direction === 'bottom') {
+ return {
+ borderTopWidth: handleWidth,
+ ...sx,
+ };
+ }
+
+ if (direction === 'left') {
+ return { borderInlineEndWidth: handleWidth, ...sx };
+ }
+};
diff --git a/invokeai/frontend/web/src/features/ui/components/common/Scrollable.tsx b/invokeai/frontend/web/src/features/ui/components/common/Scrollable.tsx
new file mode 100644
index 0000000000..f8c0b6a1ce
--- /dev/null
+++ b/invokeai/frontend/web/src/features/ui/components/common/Scrollable.tsx
@@ -0,0 +1,117 @@
+import { Box, ChakraProps } from '@chakra-ui/react';
+import { useOverlayScrollbars } from 'overlayscrollbars-react';
+import { ReactNode, useEffect, useRef } from 'react';
+
+type ScrollableProps = {
+ children: ReactNode;
+ containerProps?: ChakraProps;
+};
+
+const Scrollable = ({
+ children,
+ containerProps = {
+ width: 'full',
+ height: 'full',
+ flexShrink: 0,
+ },
+}: ScrollableProps) => {
+ const scrollableRef = useRef(null);
+ const topShadowRef = useRef(null);
+ const bottomShadowRef = useRef(null);
+
+ const [initialize, _instance] = useOverlayScrollbars({
+ defer: true,
+ events: {
+ initialized(instance) {
+ if (!topShadowRef.current || !bottomShadowRef.current) {
+ return;
+ }
+
+ const { scrollTop, scrollHeight, offsetHeight } =
+ instance.elements().content;
+
+ const scrollPercentage = scrollTop / (scrollHeight - offsetHeight);
+
+ topShadowRef.current.style.opacity = String(scrollPercentage * 5);
+
+ bottomShadowRef.current.style.opacity = String(
+ (1 - scrollPercentage) * 5
+ );
+ },
+ scroll: (_instance, event) => {
+ if (
+ !topShadowRef.current ||
+ !bottomShadowRef.current ||
+ !scrollableRef.current
+ ) {
+ return;
+ }
+
+ const { scrollTop, scrollHeight, offsetHeight } =
+ event.target as HTMLDivElement;
+
+ const scrollPercentage = scrollTop / (scrollHeight - offsetHeight);
+
+ topShadowRef.current.style.opacity = String(scrollPercentage * 5);
+
+ bottomShadowRef.current.style.opacity = String(
+ (1 - scrollPercentage) * 5
+ );
+ },
+ },
+ });
+
+ useEffect(() => {
+ if (
+ !scrollableRef.current ||
+ !topShadowRef.current ||
+ !bottomShadowRef.current
+ ) {
+ return;
+ }
+
+ topShadowRef.current.style.opacity = '0';
+
+ bottomShadowRef.current.style.opacity = '0';
+
+ initialize(scrollableRef.current);
+ }, [initialize]);
+
+ return (
+
+
+ {children}
+
+
+
+
+ );
+};
+
+export default Scrollable;
diff --git a/invokeai/frontend/web/src/features/ui/components/ImageToImage/ImageToImageDisplay.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/ImageToImage/ImageToImageContent.tsx
similarity index 94%
rename from invokeai/frontend/web/src/features/ui/components/ImageToImage/ImageToImageDisplay.tsx
rename to invokeai/frontend/web/src/features/ui/components/tabs/ImageToImage/ImageToImageContent.tsx
index 173985697f..db62d2ed80 100644
--- a/invokeai/frontend/web/src/features/ui/components/ImageToImage/ImageToImageDisplay.tsx
+++ b/invokeai/frontend/web/src/features/ui/components/tabs/ImageToImage/ImageToImageContent.tsx
@@ -14,7 +14,7 @@ const workareaSplitViewStyle: ChakraProps['sx'] = {
padding: 4,
};
-const ImageToImageDisplay = () => {
+const ImageToImageContent = () => {
const initialImage = useAppSelector(
(state: RootState) => state.generation.initialImage
);
@@ -47,4 +47,4 @@ const ImageToImageDisplay = () => {
);
};
-export default ImageToImageDisplay;
+export default ImageToImageContent;
diff --git a/invokeai/frontend/web/src/features/ui/components/ImageToImage/ImageToImagePanel.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/ImageToImage/ImageToImageParameters.tsx
similarity index 88%
rename from invokeai/frontend/web/src/features/ui/components/ImageToImage/ImageToImagePanel.tsx
rename to invokeai/frontend/web/src/features/ui/components/tabs/ImageToImage/ImageToImageParameters.tsx
index d5f2268cb8..d7c3006d32 100644
--- a/invokeai/frontend/web/src/features/ui/components/ImageToImage/ImageToImagePanel.tsx
+++ b/invokeai/frontend/web/src/features/ui/components/tabs/ImageToImage/ImageToImageParameters.tsx
@@ -15,11 +15,11 @@ import ParametersAccordion from 'features/parameters/components/ParametersAccord
import ProcessButtons from 'features/parameters/components/ProcessButtons/ProcessButtons';
import NegativePromptInput from 'features/parameters/components/PromptInput/NegativePromptInput';
import PromptInput from 'features/parameters/components/PromptInput/PromptInput';
-import InvokeOptionsPanel from 'features/ui/components/InvokeParametersPanel';
import { useTranslation } from 'react-i18next';
-import ImageToImageSettings from './ImageToImageSettings';
+import PinParametersPanelButton from 'features/ui/components/common/PinParametersPanelButton';
+import ImageToImageSettings from 'features/parameters/components/AdvancedParameters/ImageToImage/ImageToImageSettings';
-export default function ImageToImagePanel() {
+export default function ImageToImageParameters() {
const { t } = useTranslation();
const imageToImageAccordions = {
@@ -69,13 +69,12 @@ export default function ImageToImagePanel() {
};
return (
-
-
-
-
-
+
+
+
-
+
+
);
}
diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ImageToImage/ImageToImageWorkarea.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/ImageToImage/ImageToImageWorkarea.tsx
new file mode 100644
index 0000000000..f9bd582624
--- /dev/null
+++ b/invokeai/frontend/web/src/features/ui/components/tabs/ImageToImage/ImageToImageWorkarea.tsx
@@ -0,0 +1,11 @@
+import InvokeWorkarea from 'features/ui/components/common/InvokeWorkarea';
+import ImageToImageContent from './ImageToImageContent';
+import ImageToImageParameters from './ImageToImageParameters';
+
+export default function ImageToImageWorkarea() {
+ return (
+ }>
+
+
+ );
+}
diff --git a/invokeai/frontend/web/src/features/ui/components/ImageToImage/InitImagePreview.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/ImageToImage/InitImagePreview.tsx
similarity index 100%
rename from invokeai/frontend/web/src/features/ui/components/ImageToImage/InitImagePreview.tsx
rename to invokeai/frontend/web/src/features/ui/components/tabs/ImageToImage/InitImagePreview.tsx
diff --git a/invokeai/frontend/web/src/features/ui/components/TextToImage/TextToImageDisplay.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/TextToImage/TextToImageContent.tsx
similarity index 85%
rename from invokeai/frontend/web/src/features/ui/components/TextToImage/TextToImageDisplay.tsx
rename to invokeai/frontend/web/src/features/ui/components/tabs/TextToImage/TextToImageContent.tsx
index 219b70bc2d..886e3a5331 100644
--- a/invokeai/frontend/web/src/features/ui/components/TextToImage/TextToImageDisplay.tsx
+++ b/invokeai/frontend/web/src/features/ui/components/tabs/TextToImage/TextToImageContent.tsx
@@ -1,7 +1,7 @@
import { Box, Flex } from '@chakra-ui/react';
import CurrentImageDisplay from 'features/gallery/components/CurrentImageDisplay';
-const TextToImageDisplay = () => {
+const TextToImageContent = () => {
return (
{
);
};
-export default TextToImageDisplay;
+export default TextToImageContent;
diff --git a/invokeai/frontend/web/src/features/ui/components/TextToImage/TextToImagePanel.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/TextToImage/TextToImageParameters.tsx
similarity index 90%
rename from invokeai/frontend/web/src/features/ui/components/TextToImage/TextToImagePanel.tsx
rename to invokeai/frontend/web/src/features/ui/components/tabs/TextToImage/TextToImageParameters.tsx
index 09b96be20c..c970482c26 100644
--- a/invokeai/frontend/web/src/features/ui/components/TextToImage/TextToImagePanel.tsx
+++ b/invokeai/frontend/web/src/features/ui/components/tabs/TextToImage/TextToImageParameters.tsx
@@ -15,10 +15,10 @@ import ParametersAccordion from 'features/parameters/components/ParametersAccord
import ProcessButtons from 'features/parameters/components/ProcessButtons/ProcessButtons';
import NegativePromptInput from 'features/parameters/components/PromptInput/NegativePromptInput';
import PromptInput from 'features/parameters/components/PromptInput/PromptInput';
-import InvokeOptionsPanel from 'features/ui/components/InvokeParametersPanel';
import { useTranslation } from 'react-i18next';
+import PinParametersPanelButton from 'features/ui/components/common/PinParametersPanelButton';
-export default function TextToImagePanel() {
+export default function TextToImageParameters() {
const { t } = useTranslation();
const textToImageAccordions = {
@@ -63,13 +63,12 @@ export default function TextToImagePanel() {
};
return (
-
-
-
-
-
+
+
+
-
+
+
);
}
diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/TextToImage/TextToImageWorkarea.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/TextToImage/TextToImageWorkarea.tsx
new file mode 100644
index 0000000000..a1de45d8e6
--- /dev/null
+++ b/invokeai/frontend/web/src/features/ui/components/tabs/TextToImage/TextToImageWorkarea.tsx
@@ -0,0 +1,11 @@
+import InvokeWorkarea from 'features/ui/components/common/InvokeWorkarea';
+import TextToImageContent from './TextToImageContent';
+import TextToImageParameters from './TextToImageParameters';
+
+export default function TextToImageWorkarea() {
+ return (
+ }>
+
+
+ );
+}
diff --git a/invokeai/frontend/web/src/features/ui/components/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasDisplayBeta.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasContentBeta.tsx
similarity index 96%
rename from invokeai/frontend/web/src/features/ui/components/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasDisplayBeta.tsx
rename to invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasContentBeta.tsx
index 2ca4139ed0..f6382eeab7 100644
--- a/invokeai/frontend/web/src/features/ui/components/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasDisplayBeta.tsx
+++ b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasContentBeta.tsx
@@ -27,7 +27,7 @@ const selector = createSelector(
}
);
-const UnifiedCanvasDisplayBeta = () => {
+const UnifiedCanvasContentBeta = () => {
const dispatch = useAppDispatch();
const { doesCanvasNeedScaling } = useAppSelector(selector);
@@ -70,4 +70,4 @@ const UnifiedCanvasDisplayBeta = () => {
);
};
-export default UnifiedCanvasDisplayBeta;
+export default UnifiedCanvasContentBeta;
diff --git a/invokeai/frontend/web/src/features/ui/components/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasBaseBrushSettings.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasBaseBrushSettings.tsx
similarity index 100%
rename from invokeai/frontend/web/src/features/ui/components/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasBaseBrushSettings.tsx
rename to invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasBaseBrushSettings.tsx
diff --git a/invokeai/frontend/web/src/features/ui/components/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasBrushSettings.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasBrushSettings.tsx
similarity index 100%
rename from invokeai/frontend/web/src/features/ui/components/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasBrushSettings.tsx
rename to invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasBrushSettings.tsx
diff --git a/invokeai/frontend/web/src/features/ui/components/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasBrushSize.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasBrushSize.tsx
similarity index 100%
rename from invokeai/frontend/web/src/features/ui/components/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasBrushSize.tsx
rename to invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasBrushSize.tsx
diff --git a/invokeai/frontend/web/src/features/ui/components/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasClearMask.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasClearMask.tsx
similarity index 100%
rename from invokeai/frontend/web/src/features/ui/components/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasClearMask.tsx
rename to invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasClearMask.tsx
diff --git a/invokeai/frontend/web/src/features/ui/components/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasColorPicker.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasColorPicker.tsx
similarity index 100%
rename from invokeai/frontend/web/src/features/ui/components/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasColorPicker.tsx
rename to invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasColorPicker.tsx
diff --git a/invokeai/frontend/web/src/features/ui/components/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasDarkenOutsideSelection.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasDarkenOutsideSelection.tsx
similarity index 100%
rename from invokeai/frontend/web/src/features/ui/components/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasDarkenOutsideSelection.tsx
rename to invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasDarkenOutsideSelection.tsx
diff --git a/invokeai/frontend/web/src/features/ui/components/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasEnableMask.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasEnableMask.tsx
similarity index 100%
rename from invokeai/frontend/web/src/features/ui/components/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasEnableMask.tsx
rename to invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasEnableMask.tsx
diff --git a/invokeai/frontend/web/src/features/ui/components/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasLimitStrokesToBox.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasLimitStrokesToBox.tsx
similarity index 100%
rename from invokeai/frontend/web/src/features/ui/components/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasLimitStrokesToBox.tsx
rename to invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasLimitStrokesToBox.tsx
diff --git a/invokeai/frontend/web/src/features/ui/components/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasMaskBrushSettings.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasMaskBrushSettings.tsx
similarity index 100%
rename from invokeai/frontend/web/src/features/ui/components/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasMaskBrushSettings.tsx
rename to invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasMaskBrushSettings.tsx
diff --git a/invokeai/frontend/web/src/features/ui/components/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasMoveSettings.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasMoveSettings.tsx
similarity index 100%
rename from invokeai/frontend/web/src/features/ui/components/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasMoveSettings.tsx
rename to invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasMoveSettings.tsx
diff --git a/invokeai/frontend/web/src/features/ui/components/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasPreserveMask.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasPreserveMask.tsx
similarity index 100%
rename from invokeai/frontend/web/src/features/ui/components/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasPreserveMask.tsx
rename to invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasPreserveMask.tsx
diff --git a/invokeai/frontend/web/src/features/ui/components/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasSettings.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasSettings.tsx
similarity index 100%
rename from invokeai/frontend/web/src/features/ui/components/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasSettings.tsx
rename to invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasSettings.tsx
diff --git a/invokeai/frontend/web/src/features/ui/components/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasShowGrid.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasShowGrid.tsx
similarity index 100%
rename from invokeai/frontend/web/src/features/ui/components/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasShowGrid.tsx
rename to invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasShowGrid.tsx
diff --git a/invokeai/frontend/web/src/features/ui/components/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasSnapToGrid.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasSnapToGrid.tsx
similarity index 100%
rename from invokeai/frontend/web/src/features/ui/components/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasSnapToGrid.tsx
rename to invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettings/UnifiedCanvasSnapToGrid.tsx
diff --git a/invokeai/frontend/web/src/features/ui/components/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettingsBeta.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettingsBeta.tsx
similarity index 100%
rename from invokeai/frontend/web/src/features/ui/components/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettingsBeta.tsx
rename to invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolSettingsBeta.tsx
diff --git a/invokeai/frontend/web/src/features/ui/components/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolbar/UnifiedCanvasCopyToClipboard.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolbar/UnifiedCanvasCopyToClipboard.tsx
similarity index 100%
rename from invokeai/frontend/web/src/features/ui/components/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolbar/UnifiedCanvasCopyToClipboard.tsx
rename to invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolbar/UnifiedCanvasCopyToClipboard.tsx
diff --git a/invokeai/frontend/web/src/features/ui/components/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolbar/UnifiedCanvasDownloadImage.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolbar/UnifiedCanvasDownloadImage.tsx
similarity index 100%
rename from invokeai/frontend/web/src/features/ui/components/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolbar/UnifiedCanvasDownloadImage.tsx
rename to invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolbar/UnifiedCanvasDownloadImage.tsx
diff --git a/invokeai/frontend/web/src/features/ui/components/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolbar/UnifiedCanvasFileUploader.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolbar/UnifiedCanvasFileUploader.tsx
similarity index 100%
rename from invokeai/frontend/web/src/features/ui/components/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolbar/UnifiedCanvasFileUploader.tsx
rename to invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolbar/UnifiedCanvasFileUploader.tsx
diff --git a/invokeai/frontend/web/src/features/ui/components/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolbar/UnifiedCanvasLayerSelect.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolbar/UnifiedCanvasLayerSelect.tsx
similarity index 100%
rename from invokeai/frontend/web/src/features/ui/components/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolbar/UnifiedCanvasLayerSelect.tsx
rename to invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolbar/UnifiedCanvasLayerSelect.tsx
diff --git a/invokeai/frontend/web/src/features/ui/components/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolbar/UnifiedCanvasMergeVisible.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolbar/UnifiedCanvasMergeVisible.tsx
similarity index 100%
rename from invokeai/frontend/web/src/features/ui/components/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolbar/UnifiedCanvasMergeVisible.tsx
rename to invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolbar/UnifiedCanvasMergeVisible.tsx
diff --git a/invokeai/frontend/web/src/features/ui/components/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolbar/UnifiedCanvasMoveTool.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolbar/UnifiedCanvasMoveTool.tsx
similarity index 100%
rename from invokeai/frontend/web/src/features/ui/components/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolbar/UnifiedCanvasMoveTool.tsx
rename to invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolbar/UnifiedCanvasMoveTool.tsx
diff --git a/invokeai/frontend/web/src/features/ui/components/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolbar/UnifiedCanvasProcessingButtons.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolbar/UnifiedCanvasProcessingButtons.tsx
similarity index 100%
rename from invokeai/frontend/web/src/features/ui/components/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolbar/UnifiedCanvasProcessingButtons.tsx
rename to invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolbar/UnifiedCanvasProcessingButtons.tsx
diff --git a/invokeai/frontend/web/src/features/ui/components/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolbar/UnifiedCanvasResetCanvas.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolbar/UnifiedCanvasResetCanvas.tsx
similarity index 100%
rename from invokeai/frontend/web/src/features/ui/components/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolbar/UnifiedCanvasResetCanvas.tsx
rename to invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolbar/UnifiedCanvasResetCanvas.tsx
diff --git a/invokeai/frontend/web/src/features/ui/components/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolbar/UnifiedCanvasResetView.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolbar/UnifiedCanvasResetView.tsx
similarity index 100%
rename from invokeai/frontend/web/src/features/ui/components/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolbar/UnifiedCanvasResetView.tsx
rename to invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolbar/UnifiedCanvasResetView.tsx
diff --git a/invokeai/frontend/web/src/features/ui/components/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolbar/UnifiedCanvasSaveToGallery.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolbar/UnifiedCanvasSaveToGallery.tsx
similarity index 100%
rename from invokeai/frontend/web/src/features/ui/components/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolbar/UnifiedCanvasSaveToGallery.tsx
rename to invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolbar/UnifiedCanvasSaveToGallery.tsx
diff --git a/invokeai/frontend/web/src/features/ui/components/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolbar/UnifiedCanvasToolSelect.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolbar/UnifiedCanvasToolSelect.tsx
similarity index 100%
rename from invokeai/frontend/web/src/features/ui/components/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolbar/UnifiedCanvasToolSelect.tsx
rename to invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolbar/UnifiedCanvasToolSelect.tsx
diff --git a/invokeai/frontend/web/src/features/ui/components/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolbarBeta.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolbarBeta.tsx
similarity index 100%
rename from invokeai/frontend/web/src/features/ui/components/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolbarBeta.tsx
rename to invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasBeta/UnifiedCanvasToolbarBeta.tsx
diff --git a/invokeai/frontend/web/src/features/ui/components/UnifiedCanvas/UnifiedCanvasDisplay.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasContent.tsx
similarity index 96%
rename from invokeai/frontend/web/src/features/ui/components/UnifiedCanvas/UnifiedCanvasDisplay.tsx
rename to invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasContent.tsx
index ac7f086bea..66e4f8b0ad 100644
--- a/invokeai/frontend/web/src/features/ui/components/UnifiedCanvas/UnifiedCanvasDisplay.tsx
+++ b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasContent.tsx
@@ -26,7 +26,7 @@ const selector = createSelector(
}
);
-const UnifiedCanvasDisplay = () => {
+const UnifiedCanvasContent = () => {
const dispatch = useAppDispatch();
const { doesCanvasNeedScaling } = useAppSelector(selector);
@@ -80,4 +80,4 @@ const UnifiedCanvasDisplay = () => {
);
};
-export default UnifiedCanvasDisplay;
+export default UnifiedCanvasContent;
diff --git a/invokeai/frontend/web/src/features/ui/components/UnifiedCanvas/UnifiedCanvasPanel.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasParameters.tsx
similarity index 89%
rename from invokeai/frontend/web/src/features/ui/components/UnifiedCanvas/UnifiedCanvasPanel.tsx
rename to invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasParameters.tsx
index 97ba3f23ec..e7b0af7bdc 100644
--- a/invokeai/frontend/web/src/features/ui/components/UnifiedCanvas/UnifiedCanvasPanel.tsx
+++ b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasParameters.tsx
@@ -15,10 +15,10 @@ import ParametersAccordion from 'features/parameters/components/ParametersAccord
import ProcessButtons from 'features/parameters/components/ProcessButtons/ProcessButtons';
import NegativePromptInput from 'features/parameters/components/PromptInput/NegativePromptInput';
import PromptInput from 'features/parameters/components/PromptInput/PromptInput';
-import InvokeOptionsPanel from 'features/ui/components/InvokeParametersPanel';
import { useTranslation } from 'react-i18next';
+import PinParametersPanelButton from 'features/ui/components/common/PinParametersPanelButton';
-export default function UnifiedCanvasPanel() {
+export default function UnifiedCanvasParameters() {
const { t } = useTranslation();
const unifiedCanvasAccordions = {
@@ -66,14 +66,12 @@ export default function UnifiedCanvasPanel() {
};
return (
-
-
-
-
-
+
+
+
- {/* */}
-
+
+
);
}
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
new file mode 100644
index 0000000000..ae1ace0b04
--- /dev/null
+++ b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasWorkarea.tsx
@@ -0,0 +1,21 @@
+import { RootState } from 'app/store';
+import { useAppSelector } from 'app/storeHooks';
+import InvokeWorkarea from 'features/ui/components/common/InvokeWorkarea';
+import UnifiedCanvasContentBeta from './UnifiedCanvasBeta/UnifiedCanvasContentBeta';
+import UnifiedCanvasContent from './UnifiedCanvasContent';
+import UnifiedCanvasParameters from './UnifiedCanvasParameters';
+
+export default function UnifiedCanvasWorkarea() {
+ const shouldUseCanvasBetaLayout = useAppSelector(
+ (state: RootState) => state.ui.shouldUseCanvasBetaLayout
+ );
+ return (
+ }>
+ {shouldUseCanvasBetaLayout ? (
+
+ ) : (
+
+ )}
+
+ );
+}
diff --git a/invokeai/frontend/web/src/features/ui/store/uiSlice.ts b/invokeai/frontend/web/src/features/ui/store/uiSlice.ts
index d9abae23d8..60fe177172 100644
--- a/invokeai/frontend/web/src/features/ui/store/uiSlice.ts
+++ b/invokeai/frontend/web/src/features/ui/store/uiSlice.ts
@@ -7,7 +7,6 @@ const initialtabsState: UIState = {
activeTab: 0,
currentTheme: 'dark',
parametersPanelScrollPosition: 0,
- shouldHoldParametersPanelOpen: false,
shouldPinParametersPanel: true,
shouldShowParametersPanel: true,
shouldShowImageDetails: false,
@@ -41,16 +40,11 @@ export const uiSlice = createSlice({
},
setShouldPinParametersPanel: (state, action: PayloadAction) => {
state.shouldPinParametersPanel = action.payload;
+ state.shouldShowParametersPanel = true;
},
setShouldShowParametersPanel: (state, action: PayloadAction) => {
state.shouldShowParametersPanel = action.payload;
},
- setShouldHoldParametersPanelOpen: (
- state,
- action: PayloadAction
- ) => {
- state.shouldHoldParametersPanelOpen = action.payload;
- },
setShouldShowImageDetails: (state, action: PayloadAction) => {
state.shouldShowImageDetails = action.payload;
},
@@ -76,7 +70,6 @@ export const {
setActiveTab,
setCurrentTheme,
setParametersPanelScrollPosition,
- setShouldHoldParametersPanelOpen,
setShouldPinParametersPanel,
setShouldShowParametersPanel,
setShouldShowImageDetails,
diff --git a/invokeai/frontend/web/src/features/ui/store/uiTypes.ts b/invokeai/frontend/web/src/features/ui/store/uiTypes.ts
index 5885bc3ed7..d7656df78b 100644
--- a/invokeai/frontend/web/src/features/ui/store/uiTypes.ts
+++ b/invokeai/frontend/web/src/features/ui/store/uiTypes.ts
@@ -4,7 +4,6 @@ export interface UIState {
activeTab: number;
currentTheme: string;
parametersPanelScrollPosition: number;
- shouldHoldParametersPanelOpen: boolean;
shouldPinParametersPanel: boolean;
shouldShowParametersPanel: boolean;
shouldShowImageDetails: boolean;
diff --git a/invokeai/frontend/web/src/theme/components/accordion.ts b/invokeai/frontend/web/src/theme/components/accordion.ts
index 46a0c69fd7..3477548c70 100644
--- a/invokeai/frontend/web/src/theme/components/accordion.ts
+++ b/invokeai/frontend/web/src/theme/components/accordion.ts
@@ -9,7 +9,6 @@ const { definePartsStyle, defineMultiStyleConfig } =
const invokeAIContainer = defineStyle({
border: 'none',
- pt: 2,
});
const invokeAIButton = defineStyle((props) => {
@@ -40,7 +39,6 @@ const invokeAIPanel = defineStyle((props) => {
bg: `${c}.800`,
borderRadius: 'base',
borderTopRadius: 'none',
- p: 4,
};
});
diff --git a/invokeai/frontend/web/src/theme/overlayscrollbar.css b/invokeai/frontend/web/src/theme/overlayscrollbar.css
new file mode 100644
index 0000000000..8d80296978
--- /dev/null
+++ b/invokeai/frontend/web/src/theme/overlayscrollbar.css
@@ -0,0 +1,25 @@
+.os-scrollbar {
+ /* --os-size: 0; */
+ /* --os-padding-perpendicular: 0; */
+ /* --os-padding-axis: 0; */
+ /* --os-track-border-radius: 0; */
+ /* --os-track-bg: none; */
+ /* --os-track-bg-hover: none; */
+ /* --os-track-bg-active: none; */
+ /* --os-track-border: none; */
+ /* --os-track-border-hover: none; */
+ /* --os-track-border-active: none; */
+ /* --os-handle-border-radius: 0; */
+ --os-handle-bg: var(--invokeai-colors-accent-600);
+ --os-handle-bg-hover: var(--invokeai-colors-accent-550);
+ --os-handle-bg-active: var(--invokeai-colors-accent-500);
+ /* --os-handle-border: none; */
+ /* --os-handle-border-hover: none; */
+ /* --os-handle-border-active: none; */
+ /* --os-handle-min-size: 33px; */
+ /* --os-handle-max-size: none; */
+ /* --os-handle-perpendicular-size: 100%; */
+ /* --os-handle-perpendicular-size-hover: 100%; */
+ /* --os-handle-perpendicular-size-active: 100%; */
+ /* --os-handle-interactive-area-offset: 0; */
+}
diff --git a/invokeai/frontend/web/src/theme/theme.ts b/invokeai/frontend/web/src/theme/theme.ts
index 6cb658564c..b51458d33d 100644
--- a/invokeai/frontend/web/src/theme/theme.ts
+++ b/invokeai/frontend/web/src/theme/theme.ts
@@ -12,7 +12,7 @@ import { modalTheme } from './components/modal';
import { numberInputTheme } from './components/numberInput';
import { popoverTheme } from './components/popover';
import { progressTheme } from './components/progress';
-import { scrollbar } from './components/scrollbar';
+import { no_scrollbar, scrollbar } from './components/scrollbar';
import { selectTheme } from './components/select';
import { sliderTheme } from './components/slider';
import { switchTheme } from './components/switch';
@@ -31,7 +31,7 @@ export const theme: ThemeOverride = {
color: 'base.50',
overflow: 'hidden',
},
- ...scrollbar,
+ ...no_scrollbar,
}),
},
direction: 'ltr',
diff --git a/invokeai/frontend/web/src/theme/util/constants.ts b/invokeai/frontend/web/src/theme/util/constants.ts
index 85d9185f31..064c71242f 100644
--- a/invokeai/frontend/web/src/theme/util/constants.ts
+++ b/invokeai/frontend/web/src/theme/util/constants.ts
@@ -11,6 +11,9 @@ export const APP_GALLERY_HEIGHT = 'calc(100vw - 0.3rem + 5rem)';
export const APP_GALLERY_POPOVER_HEIGHT = `calc(100vh - (${APP_CONTENT_HEIGHT_CUTOFF} + 6rem))`;
export const APP_METADATA_HEIGHT = `calc(100vh - (${APP_CONTENT_HEIGHT_CUTOFF} + 4.4rem))`;
+// this is in pixels
+export const PARAMETERS_PANEL_WIDTH = 384;
+
// do not touch ffs
export const APP_TEXT_TO_IMAGE_HEIGHT =
'calc(100vh - 9.4375rem - 1.925rem - 1.15rem)';
diff --git a/invokeai/frontend/web/yarn.lock b/invokeai/frontend/web/yarn.lock
index 1a40014efe..da7f1435a5 100644
--- a/invokeai/frontend/web/yarn.lock
+++ b/invokeai/frontend/web/yarn.lock
@@ -4204,6 +4204,16 @@ os-tmpdir@~1.0.2:
resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274"
integrity sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==
+overlayscrollbars-react@^0.5.0:
+ version "0.5.0"
+ resolved "https://registry.yarnpkg.com/overlayscrollbars-react/-/overlayscrollbars-react-0.5.0.tgz#0272bdc6304c7228a58d30e5b678e97fd5c5d8dd"
+ integrity sha512-uCNTnkfWW74veoiEv3kSwoLelKt4e8gTNv65D771X3il0x5g5Yo0fUbro7SpQzR9yNgi23cvB2mQHTTdQH96pA==
+
+overlayscrollbars@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/overlayscrollbars/-/overlayscrollbars-2.1.0.tgz#d647034ef388980e0e5e092f7429c501215330a1"
+ integrity sha512-L6p4o4aWse5pDstRnJjZaos+al+bkuAgzGIlWwlsxRSgW6+7Kvrp+kAzlWoTZ1bgB4CJj+8u5bjdq8XHEhWjrw==
+
p-cancelable@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-1.1.0.tgz#d078d15a3af409220c886f1d9a0ca2e441ab26cc"