diff --git a/invokeai/frontend/web/package.json b/invokeai/frontend/web/package.json
index cecba05d6a..585d28007a 100644
--- a/invokeai/frontend/web/package.json
+++ b/invokeai/frontend/web/package.json
@@ -6,6 +6,7 @@
"prepare": "cd ../../../ && husky install invokeai/frontend/web/.husky",
"dev": "concurrently \"vite dev\" \"yarn run theme:watch\"",
"dev:nodes": "concurrently \"vite dev --mode nodes\" \"yarn run theme:watch\"",
+ "dev:host": "concurrently \"vite dev --host\" \"yarn run theme:watch\"",
"build": "yarn run lint && vite build",
"api:web": "openapi -i http://localhost:9090/openapi.json -o src/services/api --client axios --useOptions --useUnionTypes --exportSchemas true --indent 2 --request src/services/fixtures/request.ts",
"api:file": "openapi -i src/services/fixtures/openapi.json -o src/services/api --client axios --useOptions --useUnionTypes --exportSchemas true --indent 2 --request src/services/fixtures/request.ts",
diff --git a/invokeai/frontend/web/public/locales/en.json b/invokeai/frontend/web/public/locales/en.json
index 0bb6b49f8a..1b3b210b9a 100644
--- a/invokeai/frontend/web/public/locales/en.json
+++ b/invokeai/frontend/web/public/locales/en.json
@@ -19,7 +19,8 @@
"toggleAutoscroll": "Toggle autoscroll",
"toggleLogViewer": "Toggle Log Viewer",
"showGallery": "Show Gallery",
- "showOptionsPanel": "Show Options Panel"
+ "showOptionsPanel": "Show Options Panel",
+ "menu": "Menu"
},
"common": {
"hotkeysLabel": "Hotkeys",
diff --git a/invokeai/frontend/web/src/app/App.tsx b/invokeai/frontend/web/src/app/App.tsx
index 1e86c5f222..6a22610e46 100644
--- a/invokeai/frontend/web/src/app/App.tsx
+++ b/invokeai/frontend/web/src/app/App.tsx
@@ -67,7 +67,12 @@ const App = (props: Props) => {
h={APP_HEIGHT}
>
{props.children || }
-
+
diff --git a/invokeai/frontend/web/src/app/constants.ts b/invokeai/frontend/web/src/app/constants.ts
index 0771d0558a..e7cb1972fa 100644
--- a/invokeai/frontend/web/src/app/constants.ts
+++ b/invokeai/frontend/web/src/app/constants.ts
@@ -31,13 +31,13 @@ export const DIFFUSERS_SAMPLERS: Array = [
];
// Valid image widths
-export const WIDTHS: Array = Array.from(Array(65)).map(
- (_x, i) => i * 64
+export const WIDTHS: Array = Array.from(Array(64)).map(
+ (_x, i) => (i + 1) * 64
);
// Valid image heights
-export const HEIGHTS: Array = Array.from(Array(65)).map(
- (_x, i) => i * 64
+export const HEIGHTS: Array = Array.from(Array(64)).map(
+ (_x, i) => (i + 1) * 64
);
// Valid upscaling levels
diff --git a/invokeai/frontend/web/src/common/hooks/useResolution.ts b/invokeai/frontend/web/src/common/hooks/useResolution.ts
new file mode 100644
index 0000000000..96b95ee074
--- /dev/null
+++ b/invokeai/frontend/web/src/common/hooks/useResolution.ts
@@ -0,0 +1,18 @@
+import { useBreakpoint } from '@chakra-ui/react';
+
+export default function useResolution():
+ | 'mobile'
+ | 'tablet'
+ | 'desktop'
+ | 'unknown' {
+ const breakpointValue = useBreakpoint();
+
+ const mobileResolutions = ['base', 'sm'];
+ const tabletResolutions = ['md', 'lg'];
+ const desktopResolutions = ['xl', '2xl'];
+
+ if (mobileResolutions.includes(breakpointValue)) return 'mobile';
+ if (tabletResolutions.includes(breakpointValue)) return 'tablet';
+ if (desktopResolutions.includes(breakpointValue)) return 'desktop';
+ return 'unknown';
+}
diff --git a/invokeai/frontend/web/src/features/gallery/components/CurrentImageButtons.tsx b/invokeai/frontend/web/src/features/gallery/components/CurrentImageButtons.tsx
index fb6d861d8b..945e9aee1a 100644
--- a/invokeai/frontend/web/src/features/gallery/components/CurrentImageButtons.tsx
+++ b/invokeai/frontend/web/src/features/gallery/components/CurrentImageButtons.tsx
@@ -425,9 +425,10 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
return (
diff --git a/invokeai/frontend/web/src/features/gallery/components/CurrentImageHidden.tsx b/invokeai/frontend/web/src/features/gallery/components/CurrentImageHidden.tsx
index 2674f6a0ba..062cdd7c00 100644
--- a/invokeai/frontend/web/src/features/gallery/components/CurrentImageHidden.tsx
+++ b/invokeai/frontend/web/src/features/gallery/components/CurrentImageHidden.tsx
@@ -13,7 +13,7 @@ const CurrentImageHidden = () => {
color: 'base.400',
}}
>
-
+
);
};
diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageGalleryPanel.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageGalleryPanel.tsx
index 1d43cac476..ea19408c65 100644
--- a/invokeai/frontend/web/src/features/gallery/components/ImageGalleryPanel.tsx
+++ b/invokeai/frontend/web/src/features/gallery/components/ImageGalleryPanel.tsx
@@ -26,6 +26,8 @@ import {
import { isStagingSelector } from 'features/canvas/store/canvasSelectors';
import { requestCanvasRescale } from 'features/canvas/store/thunks/requestCanvasScale';
import { lightboxSelector } from 'features/lightbox/store/lightboxSelectors';
+import useResolution from 'common/hooks/useResolution';
+import { Flex } from '@chakra-ui/react';
const GALLERY_TAB_WIDTHS: Record<
InvokeTabName,
@@ -97,6 +99,8 @@ export default function ImageGalleryPanel() {
shouldPinGallery && dispatch(requestCanvasRescale());
};
+ const resolution = useResolution();
+
useHotkeys(
'g',
() => {
@@ -179,25 +183,53 @@ export default function ImageGalleryPanel() {
[galleryImageMinimumWidth]
);
- return (
-
-
-
- );
+ const calcGalleryMinHeight = () => {
+ if (resolution === 'desktop') return;
+ return 300;
+ };
+
+ const imageGalleryContent = () => {
+ return (
+
+
+
+ );
+ };
+
+ const resizableImageGalleryContent = () => {
+ return (
+
+
+
+ );
+ };
+
+ const renderImageGallery = () => {
+ if (['mobile', 'tablet'].includes(resolution)) return imageGalleryContent();
+ return resizableImageGalleryContent();
+ };
+
+ return renderImageGallery();
}
diff --git a/invokeai/frontend/web/src/features/nodes/components/NodeEditor.tsx b/invokeai/frontend/web/src/features/nodes/components/NodeEditor.tsx
index 05f8c2a893..0afd4ce6c5 100644
--- a/invokeai/frontend/web/src/features/nodes/components/NodeEditor.tsx
+++ b/invokeai/frontend/web/src/features/nodes/components/NodeEditor.tsx
@@ -11,7 +11,7 @@ const NodeEditor = () => {
sx={{
position: 'relative',
width: 'full',
- height: 'full',
+ height: { base: '100vh', xl: 'full' },
borderRadius: 'md',
bg: 'base.850',
}}
diff --git a/invokeai/frontend/web/src/features/parameters/components/PromptInput/PromptInput.tsx b/invokeai/frontend/web/src/features/parameters/components/PromptInput/PromptInput.tsx
index 3d5c38ddc5..69efddf106 100644
--- a/invokeai/frontend/web/src/features/parameters/components/PromptInput/PromptInput.tsx
+++ b/invokeai/frontend/web/src/features/parameters/components/PromptInput/PromptInput.tsx
@@ -76,7 +76,7 @@ const PromptInput = () => {
onKeyDown={handleKeyDown}
resize="vertical"
ref={promptRef}
- minH={40}
+ minH={{ base: 20, lg: 40 }}
/>
diff --git a/invokeai/frontend/web/src/features/system/components/InvokeAILogoComponent.tsx b/invokeai/frontend/web/src/features/system/components/InvokeAILogoComponent.tsx
index ac7dc39a78..2c54d9d42a 100644
--- a/invokeai/frontend/web/src/features/system/components/InvokeAILogoComponent.tsx
+++ b/invokeai/frontend/web/src/features/system/components/InvokeAILogoComponent.tsx
@@ -10,19 +10,28 @@ const InvokeAILogoComponent = () => {
return (
-
-
- invoke ai
-
-
- {appVersion}
-
+
+
+
+ invoke ai
+
+
+ {appVersion}
+
+
);
};
diff --git a/invokeai/frontend/web/src/features/system/components/SiteHeader.tsx b/invokeai/frontend/web/src/features/system/components/SiteHeader.tsx
index 3f5503ee54..f407206b67 100644
--- a/invokeai/frontend/web/src/features/system/components/SiteHeader.tsx
+++ b/invokeai/frontend/web/src/features/system/components/SiteHeader.tsx
@@ -1,126 +1,68 @@
-import { Flex, Grid, Link } from '@chakra-ui/react';
-
-import { FaBug, FaCube, FaDiscord, FaGithub, FaKeyboard } from 'react-icons/fa';
-
-import IAIIconButton from 'common/components/IAIIconButton';
-
-import HotkeysModal from './HotkeysModal/HotkeysModal';
-
-import ModelManagerModal from './ModelManager/ModelManagerModal';
+import { Flex, Grid } from '@chakra-ui/react';
+import { useState } from 'react';
import ModelSelect from './ModelSelect';
-import SettingsModal from './SettingsModal/SettingsModal';
import StatusIndicator from './StatusIndicator';
-import ThemeChanger from './ThemeChanger';
-import LanguagePicker from './LanguagePicker';
-
-import { useTranslation } from 'react-i18next';
-import { MdSettings } from 'react-icons/md';
import InvokeAILogoComponent from './InvokeAILogoComponent';
+import SiteHeaderMenu from './SiteHeaderMenu';
+import useResolution from 'common/hooks/useResolution';
+import { FaBars } from 'react-icons/fa';
+import { IAIIconButton } from 'exports';
+import { useTranslation } from 'react-i18next';
/**
* Header, includes color mode toggle, settings button, status message.
*/
const SiteHeader = () => {
+ const [menuOpened, setMenuOpened] = useState(false);
+ const resolution = useResolution();
const { t } = useTranslation();
return (
-
-
-
-
+
+
+
+
+
-
+ {resolution === 'desktop' ? (
+
+ ) : (
}
- />
-
-
-
- }
- />
-
-
-
-
-
-
-
- }
- />
-
-
-
- }
- />
-
-
-
- }
- />
-
-
-
- }
- />
-
+ icon={}
+ aria-label={t('accessibility.menu')}
+ background={menuOpened ? 'base.800' : 'none'}
+ _hover={{ background: menuOpened ? 'base.800' : 'none' }}
+ onClick={() => setMenuOpened(!menuOpened)}
+ p={0}
+ >
+ )}
+
+ {resolution !== 'desktop' && menuOpened && (
+
+
+
+ )}
);
};
diff --git a/invokeai/frontend/web/src/features/system/components/SiteHeaderMenu.tsx b/invokeai/frontend/web/src/features/system/components/SiteHeaderMenu.tsx
new file mode 100644
index 0000000000..09118b4a0f
--- /dev/null
+++ b/invokeai/frontend/web/src/features/system/components/SiteHeaderMenu.tsx
@@ -0,0 +1,113 @@
+import { Flex, Link } from '@chakra-ui/react';
+import { useTranslation } from 'react-i18next';
+import { FaCube, FaKeyboard, FaBug, FaGithub, FaDiscord } from 'react-icons/fa';
+import { MdSettings } from 'react-icons/md';
+import HotkeysModal from './HotkeysModal/HotkeysModal';
+import LanguagePicker from './LanguagePicker';
+import ModelManagerModal from './ModelManager/ModelManagerModal';
+import SettingsModal from './SettingsModal/SettingsModal';
+import ThemeChanger from './ThemeChanger';
+import IAIIconButton from 'common/components/IAIIconButton';
+
+const SiteHeaderMenu = () => {
+ const { t } = useTranslation();
+
+ return (
+
+
+ }
+ />
+
+
+
+ }
+ />
+
+
+
+
+
+
+
+ }
+ />
+
+
+
+ }
+ />
+
+
+
+ }
+ />
+
+
+
+ }
+ />
+
+
+ );
+};
+
+SiteHeaderMenu.displayName = 'SiteHeaderMenu';
+export default SiteHeaderMenu;
diff --git a/invokeai/frontend/web/src/features/ui/components/InvokeTabs.tsx b/invokeai/frontend/web/src/features/ui/components/InvokeTabs.tsx
index 8603d33d06..303e1e8837 100644
--- a/invokeai/frontend/web/src/features/ui/components/InvokeTabs.tsx
+++ b/invokeai/frontend/web/src/features/ui/components/InvokeTabs.tsx
@@ -138,9 +138,16 @@ export default function InvokeTabs() {
dispatch(setActiveTab(index));
}}
flexGrow={1}
+ flexDir={{ base: 'column', xl: 'row' }}
+ gap={{ base: 4 }}
isLazy
>
-
+
{tabs}
{tabPanels}
diff --git a/invokeai/frontend/web/src/features/ui/components/InvokeWorkarea.tsx b/invokeai/frontend/web/src/features/ui/components/InvokeWorkarea.tsx
index 13f551e904..8ed443a345 100644
--- a/invokeai/frontend/web/src/features/ui/components/InvokeWorkarea.tsx
+++ b/invokeai/frontend/web/src/features/ui/components/InvokeWorkarea.tsx
@@ -1,4 +1,4 @@
-import { Box, BoxProps, Flex } from '@chakra-ui/react';
+import { Box, BoxProps, Grid, GridItem } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit';
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import { initialImageSelected } from 'features/parameters/store/generationSlice';
@@ -52,12 +52,32 @@ const InvokeWorkarea = (props: InvokeWorkareaProps) => {
};
return (
-
+
{parametersPanelContent}
-
- {children}
-
-
+
+
+ {children}
+
+
+
);
};
diff --git a/invokeai/frontend/web/src/features/ui/components/ParametersPanel.tsx b/invokeai/frontend/web/src/features/ui/components/ParametersPanel.tsx
index 77cffa814a..09d4d6c316 100644
--- a/invokeai/frontend/web/src/features/ui/components/ParametersPanel.tsx
+++ b/invokeai/frontend/web/src/features/ui/components/ParametersPanel.tsx
@@ -19,6 +19,7 @@ import { createSelector } from '@reduxjs/toolkit';
import { activeTabNameSelector, uiSelector } from '../store/uiSelectors';
import { isEqual } from 'lodash';
import { lightboxSelector } from 'features/lightbox/store/lightboxSelectors';
+import useResolution from 'common/hooks/useResolution';
const parametersPanelSelector = createSelector(
[uiSelector, activeTabNameSelector, lightboxSelector],
@@ -58,6 +59,8 @@ const ParametersPanel = ({ children }: ParametersPanelProps) => {
dispatch(setShouldShowParametersPanel(false));
};
+ const resolution = useResolution();
+
useHotkeys(
'o',
() => {
@@ -88,21 +91,16 @@ const ParametersPanel = ({ children }: ParametersPanelProps) => {
},
[]
);
- return (
-
-
+
+ const parametersPanelContent = () => {
+ return (
+
{!shouldPinParametersPanel && (
{
alignItems="center"
>
-
+ {resolution == 'desktop' && }
)}
{children}
- {shouldPinParametersPanel && (
+ {shouldPinParametersPanel && resolution == 'desktop' && (
)}
-
- );
+ );
+ };
+
+ const resizableParametersPanelContent = () => {
+ return (
+
+ {parametersPanelContent()}
+
+ );
+ };
+
+ const renderParametersPanel = () => {
+ if (['mobile', 'tablet'].includes(resolution))
+ return parametersPanelContent();
+ return resizableParametersPanelContent();
+ };
+
+ return renderParametersPanel();
};
export default memo(ParametersPanel);
diff --git a/invokeai/frontend/web/src/features/ui/components/PinParametersPanelButton.tsx b/invokeai/frontend/web/src/features/ui/components/PinParametersPanelButton.tsx
index 66d71aabe3..a385f29c35 100644
--- a/invokeai/frontend/web/src/features/ui/components/PinParametersPanelButton.tsx
+++ b/invokeai/frontend/web/src/features/ui/components/PinParametersPanelButton.tsx
@@ -33,6 +33,7 @@ const PinParametersPanelButton = (props: PinParametersPanelButtonProps) => {
icon={shouldPinParametersPanel ? : }
variant="ghost"
size="sm"
+ px={{ base: 10, xl: 0 }}
sx={{
color: 'base.700',
_hover: {
diff --git a/invokeai/frontend/web/src/theme/theme.ts b/invokeai/frontend/web/src/theme/theme.ts
index 442d76f12f..1ac868c272 100644
--- a/invokeai/frontend/web/src/theme/theme.ts
+++ b/invokeai/frontend/web/src/theme/theme.ts
@@ -29,7 +29,10 @@ export const theme: ThemeOverride = {
body: {
bg: 'base.900',
color: 'base.50',
- overflow: 'hidden',
+ overflow: {
+ base: 'scroll',
+ xl: 'hidden',
+ },
},
'*': { ...no_scrollbar },
}),
@@ -38,6 +41,14 @@ export const theme: ThemeOverride = {
fonts: {
body: `'Inter', sans-serif`,
},
+ breakpoints: {
+ base: '0em', // 0px and onwards
+ sm: '30em', // 480px and onwards
+ md: '48em', // 768px and onwards
+ lg: '62em', // 992px and onwards
+ xl: '80em', // 1280px and onwards
+ '2xl': '96em', // 1536px and onwards
+ },
shadows: {
light: {
accent: `0 0 10px 0 var(--invokeai-colors-accent-300)`,