From 85a4d378837bad69632d24fe70dfa50cf5652dfb Mon Sep 17 00:00:00 2001
From: Mary Hipp <maryhipp@Marys-MacBook-Air.local>
Date: Fri, 30 Jun 2023 13:26:46 -0400
Subject: [PATCH] remove long loading state, introduce loading to gallery and
 model list

---
 .../frontend/web/src/app/components/App.tsx   | 56 +-------------
 .../components/ImageGalleryContent.tsx        | 75 +++++++++++++------
 .../src/features/gallery/store/imagesSlice.ts |  2 +-
 .../system/components/ModelSelect.tsx         | 11 ++-
 4 files changed, 63 insertions(+), 81 deletions(-)

diff --git a/invokeai/frontend/web/src/app/components/App.tsx b/invokeai/frontend/web/src/app/components/App.tsx
index 5b3cf5925f..67d2bb2a4b 100644
--- a/invokeai/frontend/web/src/app/components/App.tsx
+++ b/invokeai/frontend/web/src/app/components/App.tsx
@@ -1,9 +1,8 @@
-import { Box, Flex, Grid, Portal } from '@chakra-ui/react';
+import { Flex, Grid, Portal } from '@chakra-ui/react';
 import { useLogger } from 'app/logging/useLogger';
 import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
 import { PartialAppConfig } from 'app/types/invokeai';
 import ImageUploader from 'common/components/ImageUploader';
-import Loading from 'common/components/Loading/Loading';
 import GalleryDrawer from 'features/gallery/components/GalleryPanel';
 import Lightbox from 'features/lightbox/components/Lightbox';
 import SiteHeader from 'features/system/components/SiteHeader';
@@ -15,10 +14,8 @@ import FloatingGalleryButton from 'features/ui/components/FloatingGalleryButton'
 import FloatingParametersPanelButtons from 'features/ui/components/FloatingParametersPanelButtons';
 import InvokeTabs from 'features/ui/components/InvokeTabs';
 import ParametersDrawer from 'features/ui/components/ParametersDrawer';
-import { AnimatePresence, motion } from 'framer-motion';
 import i18n from 'i18n';
 import { ReactNode, memo, useCallback, useEffect, useState } from 'react';
-import { APP_HEIGHT, APP_WIDTH } from 'theme/util/constants';
 import GlobalHotkeys from './GlobalHotkeys';
 import Toaster from './Toaster';
 import DeleteImageModal from 'features/gallery/components/DeleteImageModal';
@@ -59,9 +56,6 @@ const App = ({
   const { data: embeddingModels } = useListModelsQuery({
     model_type: 'embedding',
   });
-
-  const [loadingOverridden, setLoadingOverridden] = useState(false);
-
   const dispatch = useAppDispatch();
 
   useEffect(() => {
@@ -73,27 +67,6 @@ const App = ({
     dispatch(configChanged(config));
   }, [dispatch, config, log]);
 
-  const handleOverrideClicked = useCallback(() => {
-    setLoadingOverridden(true);
-  }, []);
-
-  useEffect(() => {
-    if (isApplicationReady && setIsReady) {
-      setIsReady(true);
-    }
-
-    if (isApplicationReady) {
-      // TODO: This is a jank fix for canvas not filling the screen on first load
-      setTimeout(() => {
-        dispatch(requestCanvasRescale());
-      }, 200);
-    }
-
-    return () => {
-      setIsReady && setIsReady(false);
-    };
-  }, [dispatch, isApplicationReady, setIsReady]);
-
   return (
     <>
       <Grid w="100vw" h="100vh" position="relative" overflow="hidden">
@@ -123,33 +96,6 @@ const App = ({
 
         <GalleryDrawer />
         <ParametersDrawer />
-
-        <AnimatePresence>
-          {!isApplicationReady && !loadingOverridden && (
-            <motion.div
-              key="loading"
-              initial={{ opacity: 1 }}
-              animate={{ opacity: 1 }}
-              exit={{ opacity: 0 }}
-              transition={{ duration: 0.3 }}
-              style={{ zIndex: 3 }}
-            >
-              <Box position="absolute" top={0} left={0} w="100vw" h="100vh">
-                <Loading />
-              </Box>
-              <Box
-                onClick={handleOverrideClicked}
-                position="absolute"
-                top={0}
-                right={0}
-                cursor="pointer"
-                w="2rem"
-                h="2rem"
-              />
-            </motion.div>
-          )}
-        </AnimatePresence>
-
         <Portal>
           <FloatingParametersPanelButtons />
         </Portal>
diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx
index a22eb6d20f..8cfce280b0 100644
--- a/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx
+++ b/invokeai/frontend/web/src/features/gallery/components/ImageGalleryContent.tsx
@@ -6,6 +6,7 @@ import {
   FlexProps,
   Grid,
   Icon,
+  Skeleton,
   Text,
   VStack,
   forwardRef,
@@ -233,7 +234,7 @@ const ImageGalleryContent = () => {
         borderRadius: 'base',
       }}
     >
-      <Box sx={{ w: 'full' }}>
+      <Box sx={{ w: 'full', minWidth: '200px' }}>
         <Flex
           ref={resizeObserverRef}
           sx={{
@@ -355,7 +356,9 @@ const ImageGalleryContent = () => {
         </Box>
       </Box>
       <Flex direction="column" gap={2} h="full" w="full">
-        {images.length || areMoreAvailable ? (
+        {isLoading ? (
+          <LoadingGallery />
+        ) : images.length || areMoreAvailable ? (
           <>
             <Box ref={rootRef} data-overlayscrollbars="" h="100%">
               {shouldUseSingleGalleryColumn ? (
@@ -407,27 +410,7 @@ const ImageGalleryContent = () => {
             </IAIButton>
           </>
         ) : (
-          <Flex
-            sx={{
-              flexDirection: 'column',
-              alignItems: 'center',
-              justifyContent: 'center',
-              gap: 2,
-              padding: 8,
-              h: '100%',
-              w: '100%',
-              color: 'base.500',
-            }}
-          >
-            <Icon
-              as={MdPhotoLibrary}
-              sx={{
-                w: 16,
-                h: 16,
-              }}
-            />
-            <Text textAlign="center">{t('gallery.noImagesInGallery')}</Text>
-          </Flex>
+          <EmptyGallery />
         )}
       </Flex>
     </VStack>
@@ -462,4 +445,50 @@ const ListContainer = forwardRef((props: ListContainerProps, ref) => {
   );
 });
 
+const LoadingGallery = () => {
+  return (
+    <Box data-overlayscrollbars="" h="100%">
+      <VirtuosoGrid
+        style={{ height: '100%' }}
+        data={new Array(20)}
+        components={{
+          Item: ItemContainer,
+          List: ListContainer,
+        }}
+        itemContent={(index, item) => (
+          <Flex sx={{ pb: 2 }}>
+            <Skeleton sx={{ width: 'full', paddingBottom: '100%' }} />
+          </Flex>
+        )}
+      />
+    </Box>
+  );
+};
+const EmptyGallery = () => {
+  const { t } = useTranslation();
+  return (
+    <Flex
+      sx={{
+        flexDirection: 'column',
+        alignItems: 'center',
+        justifyContent: 'center',
+        gap: 2,
+        padding: 8,
+        h: '100%',
+        w: '100%',
+        color: 'base.500',
+      }}
+    >
+      <Icon
+        as={MdPhotoLibrary}
+        sx={{
+          w: 16,
+          h: 16,
+        }}
+      />
+      <Text textAlign="center">{t('gallery.noImagesInGallery')}</Text>
+    </Flex>
+  );
+};
+
 export default memo(ImageGalleryContent);
diff --git a/invokeai/frontend/web/src/features/gallery/store/imagesSlice.ts b/invokeai/frontend/web/src/features/gallery/store/imagesSlice.ts
index 8041ffd5c5..3c14d91994 100644
--- a/invokeai/frontend/web/src/features/gallery/store/imagesSlice.ts
+++ b/invokeai/frontend/web/src/features/gallery/store/imagesSlice.ts
@@ -41,7 +41,7 @@ export const initialImagesState =
     offset: 0,
     limit: 0,
     total: 0,
-    isLoading: false,
+    isLoading: true,
     categories: IMAGE_CATEGORIES,
   });
 
diff --git a/invokeai/frontend/web/src/features/system/components/ModelSelect.tsx b/invokeai/frontend/web/src/features/system/components/ModelSelect.tsx
index f9eda624f2..2a14af32b2 100644
--- a/invokeai/frontend/web/src/features/system/components/ModelSelect.tsx
+++ b/invokeai/frontend/web/src/features/system/components/ModelSelect.tsx
@@ -23,7 +23,7 @@ const ModelSelect = () => {
     (state: RootState) => state.generation.model
   );
 
-  const { data: pipelineModels } = useListModelsQuery({
+  const { data: pipelineModels, isLoading } = useListModelsQuery({
     model_type: 'main',
   });
 
@@ -78,7 +78,14 @@ const ModelSelect = () => {
     handleChangeModel(firstModel);
   }, [handleChangeModel, pipelineModels?.ids, selectedModelId]);
 
-  return (
+  return isLoading ? (
+    <IAIMantineSelect
+      label={t('modelManager.model')}
+      placeholder="Loading..."
+      disabled={true}
+      data={[]}
+    />
+  ) : (
     <IAIMantineSelect
       tooltip={selectedModel?.description}
       label={t('modelManager.model')}