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-logo - - invoke ai - - - {appVersion} - + invoke-ai-logo + + + 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)`,