diff --git a/invokeai/frontend/web/src/Loading.tsx b/invokeai/frontend/web/src/Loading.tsx
deleted file mode 100644
index 671c2cd640..0000000000
--- a/invokeai/frontend/web/src/Loading.tsx
+++ /dev/null
@@ -1,39 +0,0 @@
-import { Flex, Spinner, Text } from '@chakra-ui/react';
-import { useTranslation } from 'react-i18next';
-
-interface LoaderProps {
- showText?: boolean;
- text?: string;
-}
-
-// This component loads before the theme so we cannot use theme tokens here
-
-const Loading = (props: LoaderProps) => {
- const { t } = useTranslation();
- const { showText = false, text = t('common.loadingInvokeAI') } = props;
-
- return (
-
-
- {showText && (
-
- {text}
-
- )}
-
- );
-};
-
-export default Loading;
diff --git a/invokeai/frontend/web/src/app/App.tsx b/invokeai/frontend/web/src/app/App.tsx
index d2287136c3..4bbf8f4429 100644
--- a/invokeai/frontend/web/src/app/App.tsx
+++ b/invokeai/frontend/web/src/app/App.tsx
@@ -15,17 +15,24 @@ import ImageGalleryPanel from 'features/gallery/components/ImageGalleryPanel';
import Lightbox from 'features/lightbox/components/Lightbox';
import { useAppDispatch, useAppSelector } from './storeHooks';
import { PropsWithChildren, useEffect } from 'react';
-import { setDisabledPanels, setDisabledTabs } from 'features/ui/store/uiSlice';
import { InvokeTabName } from 'features/ui/store/tabMap';
import { shouldTransformUrlsChanged } from 'features/system/store/systemSlice';
import { setShouldFetchImages } from 'features/gallery/store/resultsSlice';
+import { motion, AnimatePresence } from 'framer-motion';
+import Loading from 'common/components/Loading/Loading';
+import {
+ ApplicationFeature,
+ disabledFeaturesChanged,
+ disabledTabsChanged,
+} from 'features/system/store/systemSlice';
+import { useIsApplicationReady } from 'features/system/hooks/useIsApplicationReady';
keepGUIAlive();
interface Props extends PropsWithChildren {
options: {
- disabledPanels: string[];
disabledTabs: InvokeTabName[];
+ disabledFeatures: ApplicationFeature[];
shouldTransformUrls?: boolean;
shouldFetchImages: boolean;
};
@@ -35,15 +42,21 @@ const App = (props: Props) => {
useToastWatcher();
const currentTheme = useAppSelector((state) => state.ui.currentTheme);
+ const disabledFeatures = useAppSelector(
+ (state) => state.system.disabledFeatures
+ );
+
+ const isApplicationReady = useIsApplicationReady();
+
const { setColorMode } = useColorMode();
const dispatch = useAppDispatch();
useEffect(() => {
- dispatch(setDisabledPanels(props.options.disabledPanels));
- }, [dispatch, props.options.disabledPanels]);
+ dispatch(disabledFeaturesChanged(props.options.disabledFeatures));
+ }, [dispatch, props.options.disabledFeatures]);
useEffect(() => {
- dispatch(setDisabledTabs(props.options.disabledTabs));
+ dispatch(disabledTabsChanged(props.options.disabledTabs));
}, [dispatch, props.options.disabledTabs]);
useEffect(() => {
@@ -61,8 +74,8 @@ const App = (props: Props) => {
}, [setColorMode, currentTheme]);
return (
-
-
+
+ {!disabledFeatures.includes('lightbox') && }
{
-
-
-
+
+
+ {!isApplicationReady && (
+
+
+
+
+
+ )}
+
+
+
+
+
);
};
diff --git a/invokeai/frontend/web/src/common/components/Loading/Loading.tsx b/invokeai/frontend/web/src/common/components/Loading/Loading.tsx
new file mode 100644
index 0000000000..16c646b0a3
--- /dev/null
+++ b/invokeai/frontend/web/src/common/components/Loading/Loading.tsx
@@ -0,0 +1,21 @@
+import { Flex, Image } from '@chakra-ui/react';
+import InvokeAILogoImage from 'assets/images/logo.png';
+
+// This component loads before the theme so we cannot use theme tokens here
+
+const Loading = () => {
+ return (
+
+
+
+ );
+};
+
+export default Loading;
diff --git a/invokeai/frontend/web/src/component.tsx b/invokeai/frontend/web/src/component.tsx
index c4cd876fbd..03ea485588 100644
--- a/invokeai/frontend/web/src/component.tsx
+++ b/invokeai/frontend/web/src/component.tsx
@@ -15,11 +15,12 @@ import '@fontsource/inter/700.css';
import '@fontsource/inter/800.css';
import '@fontsource/inter/900.css';
-import Loading from './Loading';
+import Loading from './common/components/Loading/Loading';
// Localization
import './i18n';
import { addMiddleware, resetMiddlewares } from 'redux-dynamic-middlewares';
+import { ApplicationFeature } from 'features/system/store/systemSlice';
const App = lazy(() => import('./app/App'));
const ThemeLocaleProvider = lazy(() => import('./app/ThemeLocaleProvider'));
@@ -28,6 +29,7 @@ interface Props extends PropsWithChildren {
apiUrl?: string;
disabledPanels?: string[];
disabledTabs?: InvokeTabName[];
+ disabledFeatures?: ApplicationFeature[];
token?: string;
shouldTransformUrls?: boolean;
shouldFetchImages?: boolean;
@@ -35,8 +37,15 @@ interface Props extends PropsWithChildren {
export default function Component({
apiUrl,
- disabledPanels = [],
disabledTabs = [],
+ disabledFeatures = [
+ 'lightbox',
+ 'bugLink',
+ 'discordLink',
+ 'githubLink',
+ 'localization',
+ 'modelManager',
+ ],
token,
children,
shouldTransformUrls,
@@ -69,12 +78,12 @@ export default function Component({
} persistor={persistor}>
- }>
+ }>
{
activeTabName,
shouldHidePreview,
selectedImage,
+ disabledFeatures,
} = useAppSelector(currentImageButtonsSelector);
+
const { getUrl, shouldTransformUrls } = useGetUrl();
const toast = useToast();
@@ -328,24 +333,19 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
useHotkeys(
'Shift+U',
() => {
- if (
- isESRGANAvailable &&
- !shouldDisableToolbarButtons &&
- isConnected &&
- !isProcessing &&
- upscalingLevel
- ) {
- handleClickUpscale();
- } else {
- toast({
- title: t('toast.upscalingFailed'),
- status: 'error',
- duration: 2500,
- isClosable: true,
- });
- }
+ handleClickUpscale();
+ },
+ {
+ enabled: () =>
+ disabledFeatures.includes('upscaling') ||
+ !isESRGANAvailable ||
+ shouldDisableToolbarButtons ||
+ !isConnected ||
+ isProcessing ||
+ !upscalingLevel,
},
[
+ disabledFeatures,
selectedImage,
isESRGANAvailable,
shouldDisableToolbarButtons,
@@ -362,24 +362,20 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
useHotkeys(
'Shift+R',
() => {
- if (
- isGFPGANAvailable &&
- !shouldDisableToolbarButtons &&
- isConnected &&
- !isProcessing &&
- facetoolStrength
- ) {
- handleClickFixFaces();
- } else {
- toast({
- title: t('toast.faceRestoreFailed'),
- status: 'error',
- duration: 2500,
- isClosable: true,
- });
- }
+ handleClickFixFaces();
},
+ {
+ enabled: () =>
+ disabledFeatures.includes('faceRestore') ||
+ !isGFPGANAvailable ||
+ shouldDisableToolbarButtons ||
+ !isConnected ||
+ isProcessing ||
+ !facetoolStrength,
+ },
+
[
+ disabledFeatures,
selectedImage,
isGFPGANAvailable,
shouldDisableToolbarButtons,
@@ -509,21 +505,23 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
isChecked={shouldHidePreview}
onClick={handlePreviewVisibility}
/>
- }
- tooltip={
- !isLightboxOpen
- ? `${t('parameters.openInViewer')} (Z)`
- : `${t('parameters.closeViewer')} (Z)`
- }
- aria-label={
- !isLightboxOpen
- ? `${t('parameters.openInViewer')} (Z)`
- : `${t('parameters.closeViewer')} (Z)`
- }
- isChecked={isLightboxOpen}
- onClick={handleLightBox}
- />
+ {!disabledFeatures.includes('lightbox') && (
+ }
+ tooltip={
+ !isLightboxOpen
+ ? `${t('parameters.openInViewer')} (Z)`
+ : `${t('parameters.closeViewer')} (Z)`
+ }
+ aria-label={
+ !isLightboxOpen
+ ? `${t('parameters.openInViewer')} (Z)`
+ : `${t('parameters.closeViewer')} (Z)`
+ }
+ isChecked={isLightboxOpen}
+ onClick={handleLightBox}
+ />
+ )}
@@ -556,65 +554,74 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
/>
-
- }
- aria-label={t('parameters.restoreFaces')}
- />
- }
- >
-
-
-
+ {!disabledFeatures.includes('faceRestore') && (
+ }
+ aria-label={t('parameters.restoreFaces')}
+ />
}
- onClick={handleClickFixFaces}
>
- {t('parameters.restoreFaces')}
-
-
-
+
+
+
+ {t('parameters.restoreFaces')}
+
+
+
+ )}
- }
- aria-label={t('parameters.upscale')}
- />
- }
- >
-
-
- }
+ aria-label={t('parameters.upscale')}
+ />
}
- onClick={handleClickUpscale}
>
- {t('parameters.upscaleImage')}
-
-
-
-
+
+
+
+ {t('parameters.upscaleImage')}
+
+
+
+ )}
+
+ )}
{
galleryImageMinimumWidth,
mayDeleteImage,
shouldUseSingleGalleryColumn,
+ disabledFeatures,
} = useAppSelector(hoverableImageSelector);
const { image, isSelected } = props;
const { url, thumbnail, name, metadata } = image;
@@ -133,7 +134,6 @@ const HoverableImage = memo((props: HoverableImageProps) => {
metadata.sd_metadata?.image?.init_image_path
);
if (response.ok) {
- dispatch(setActiveTab('img2img'));
dispatch(setAllImageToImageParameters(metadata?.sd_metadata));
toast({
title: t('toast.initialImageSet'),
@@ -174,9 +174,11 @@ const HoverableImage = memo((props: HoverableImageProps) => {
menuProps={{ size: 'sm', isLazy: true }}
renderMenu={() => (
-
+ {!disabledFeatures.includes('lightbox') && (
+
+ )}