From 3f152c5e8cc8dd01d0000ea008eb187c93f8101e Mon Sep 17 00:00:00 2001 From: Mary Hipp Date: Thu, 19 Sep 2024 14:56:20 -0400 Subject: [PATCH 1/8] wip --- invokeai/frontend/web/public/locales/en.json | 11 +++++ .../ImageViewer/CurrentImagePreview.tsx | 6 ++- .../ImageViewer/NoContentForViewer.tsx | 47 +++++++++++++++++++ .../gallery/hooks/useGalleryNavigation.ts | 35 ++------------ .../features/gallery/hooks/useHasImages.ts | 20 ++++++++ .../components/NotificationIcon.tsx | 18 +++++++ .../components/canvasV2Announcement.tsx | 34 ++++++++++++++ .../features/ui/components/Notifications.tsx | 37 +++++++++++++++ .../features/ui/components/VerticalNavBar.tsx | 2 + 9 files changed, 179 insertions(+), 31 deletions(-) create mode 100644 invokeai/frontend/web/src/features/gallery/components/ImageViewer/NoContentForViewer.tsx create mode 100644 invokeai/frontend/web/src/features/gallery/hooks/useHasImages.ts create mode 100644 invokeai/frontend/web/src/features/notifications/components/NotificationIcon.tsx create mode 100644 invokeai/frontend/web/src/features/notifications/components/canvasV2Announcement.tsx create mode 100644 invokeai/frontend/web/src/features/ui/components/Notifications.tsx diff --git a/invokeai/frontend/web/public/locales/en.json b/invokeai/frontend/web/public/locales/en.json index f773b1f7b4b..4165555e0ae 100644 --- a/invokeai/frontend/web/public/locales/en.json +++ b/invokeai/frontend/web/public/locales/en.json @@ -2073,5 +2073,16 @@ "metadata": "Metadata" }, "showSendingToAlerts": "Alert When Sending to Different View" + }, + "whatsNew": { + "whatsNewInInvoke": "What's New in Invoke", + "canvasV2Announcement": { + "newCanvas": "A powerful new control canvas", + "newLayerTypes": "New layer types for even more control", + "fluxSupport": "Support for the Flux family of models", + "readReleaseNotes": "Read Release Notes", + "watchReleaseVideo": "Watch Release Video", + "watchUiUpdatesOverview": "Watch UI Updates Overview" + } } } diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/CurrentImagePreview.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/CurrentImagePreview.tsx index e0875e41971..52b2b49d258 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/CurrentImagePreview.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/CurrentImagePreview.tsx @@ -18,6 +18,8 @@ import { useGetImageDTOQuery } from 'services/api/endpoints/images'; import { $hasProgress, $isProgressFromCanvas } from 'services/events/stores'; import ProgressImage from './ProgressImage'; +import { useHasImages } from '../../hooks/useHasImages'; +import { NoContentForViewer } from './NoContentForViewer'; const CurrentImagePreview = () => { const { t } = useTranslation(); @@ -27,6 +29,8 @@ const CurrentImagePreview = () => { const isProgressFromCanvas = useStore($isProgressFromCanvas); const shouldShowProgressInViewer = useAppSelector(selectShouldShowProgressInViewer); + const hasImages = useHasImages(); + const { currentData: imageDTO } = useGetImageDTOQuery(imageName ?? skipToken); const draggableData = useMemo(() => { @@ -72,7 +76,7 @@ const CurrentImagePreview = () => { isUploadDisabled={true} fitContainer useThumbailFallback - noContentFallback={} + noContentFallback={} dataTestId="image-preview" /> )} diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/NoContentForViewer.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/NoContentForViewer.tsx new file mode 100644 index 00000000000..ffac7863975 --- /dev/null +++ b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/NoContentForViewer.tsx @@ -0,0 +1,47 @@ +import { Flex, Image, Text } from '@invoke-ai/ui-library'; +import { useHasImages } from 'features/gallery/hooks/useHasImages'; +import InvokeSymbol from 'public/assets/images/invoke-symbol-char-lrg.svg'; +import { useTranslation } from 'react-i18next'; + +export const NoContentForViewer = () => { + const hasImages = useHasImages(); + const { t } = useTranslation(); + + // if (hasImages()) { + // return ; + // } + return ( + + + + To get started, enter a prompt in the box and click{' '} + + Invoke + {' '} + to generate your first image. You can choose to save your images directly to the{' '} + + Gallery + {' '} + or edit them on the{' '} + + Canvas + + . + + + Want more guidance? Check out our{' '} + + Getting Started Series + {' '} + for tips on unlocking the full potential of the Invoke Studio. + + + ); +}; diff --git a/invokeai/frontend/web/src/features/gallery/hooks/useGalleryNavigation.ts b/invokeai/frontend/web/src/features/gallery/hooks/useGalleryNavigation.ts index 4cb4d0170b8..1e98123e38d 100644 --- a/invokeai/frontend/web/src/features/gallery/hooks/useGalleryNavigation.ts +++ b/invokeai/frontend/web/src/features/gallery/hooks/useGalleryNavigation.ts @@ -29,38 +29,13 @@ import type { ImageDTO } from 'services/api/types'; * Gets the number of images per row in the gallery by grabbing their DOM elements. */ const getImagesPerRow = (): number => { - const imageEl = document.querySelector(`.${GALLERY_IMAGE_CLASS_NAME}`); - const gridEl = document.querySelector(`.${GALLERY_GRID_CLASS_NAME}`); + const widthOfGalleryImage = + document.querySelector(`.${GALLERY_IMAGE_CLASS_NAME}`)?.getBoundingClientRect().width ?? 1; - if (!imageEl || !gridEl) { - return 0; - } - const container = gridEl.parentElement; - if (!container) { - return 0; - } - - const imageRect = imageEl.getBoundingClientRect(); - const containerRect = container.getBoundingClientRect(); - - // We need to account for the gap between images - const gridElStyle = window.getComputedStyle(gridEl); - const gap = parseFloat(gridElStyle.gap); - - let imagesPerRow = 0; - let spaceUsed = 0; - - // Floating point precision can cause imagesPerRow to be 1 too small. Adding 1px to the container size fixes - // this, without the possibility of accidentally adding an extra column. - while (spaceUsed + imageRect.width <= containerRect.width + 1) { - imagesPerRow++; // Increment the number of images - spaceUsed += imageRect.width; // Add image size to the used space - if (spaceUsed + gap <= containerRect.width) { - spaceUsed += gap; // Add gap size to the used space after each image except after the last image - } - } + const widthOfGalleryGrid = document.querySelector(`.${GALLERY_GRID_CLASS_NAME}`)?.getBoundingClientRect().width ?? 0; - return imagesPerRow; + const imagesPerRow = Math.round(widthOfGalleryGrid / widthOfGalleryImage); + return imagesPerRow }; /** diff --git a/invokeai/frontend/web/src/features/gallery/hooks/useHasImages.ts b/invokeai/frontend/web/src/features/gallery/hooks/useHasImages.ts new file mode 100644 index 00000000000..97e9104a861 --- /dev/null +++ b/invokeai/frontend/web/src/features/gallery/hooks/useHasImages.ts @@ -0,0 +1,20 @@ +import { selectListImagesQueryArgs } from "features/gallery/store/gallerySelectors"; +import { useCallback } from "react" +import { useListAllBoardsQuery } from "services/api/endpoints/boards"; +import { useListImagesQuery } from "services/api/endpoints/images"; + +export const useHasImages = () => { + const { data: boardList } = useListAllBoardsQuery({ include_archived: true }) + const { data: uncategorizedImages } = useListImagesQuery({ ...selectListImagesQueryArgs, board_id: "none" }) + + const hasImages = useCallback(() => { + const hasBoards = boardList && boardList.length > 0; + if (!hasBoards) { + return uncategorizedImages ? uncategorizedImages.total > 0 : false + } else { + return boardList.filter(board => board.image_count > 0).length > 0 + } + }, [boardList, uncategorizedImages]) + + return hasImages +} \ No newline at end of file diff --git a/invokeai/frontend/web/src/features/notifications/components/NotificationIcon.tsx b/invokeai/frontend/web/src/features/notifications/components/NotificationIcon.tsx new file mode 100644 index 00000000000..58cf4c150b1 --- /dev/null +++ b/invokeai/frontend/web/src/features/notifications/components/NotificationIcon.tsx @@ -0,0 +1,18 @@ +import { Box,Flex, IconButton } from '@invoke-ai/ui-library'; +import { PiLightbulbFilamentBold } from 'react-icons/pi'; + +export const NotificationIcon = ({ showIndicator }: { showIndicator: boolean }) => { + return ( + + } + boxSize={8} + /> + {showIndicator && ( + + )} + + ); +}; diff --git a/invokeai/frontend/web/src/features/notifications/components/canvasV2Announcement.tsx b/invokeai/frontend/web/src/features/notifications/components/canvasV2Announcement.tsx new file mode 100644 index 00000000000..2b07979d67e --- /dev/null +++ b/invokeai/frontend/web/src/features/notifications/components/canvasV2Announcement.tsx @@ -0,0 +1,34 @@ +import { Flex, Icon, ListItem, Text,UnorderedList } from '@invoke-ai/ui-library'; +import { PiArrowSquareOutBold } from 'react-icons/pi'; + +export const CanvasV2Announcement = () => { + return ( + + + A poweful new control canvas + New layer types for even more control + Support for the Flux family of models + + + + + Read Release Notes + + + + + + Watch Release Video + + + + + + Watch UI Updates Overview + + + + + + ); +}; diff --git a/invokeai/frontend/web/src/features/ui/components/Notifications.tsx b/invokeai/frontend/web/src/features/ui/components/Notifications.tsx new file mode 100644 index 00000000000..77ff2858ece --- /dev/null +++ b/invokeai/frontend/web/src/features/ui/components/Notifications.tsx @@ -0,0 +1,37 @@ +import { + Flex, + Image, + Popover, + PopoverBody, + PopoverCloseButton, + PopoverContent, + PopoverHeader, + PopoverTrigger, +} from '@invoke-ai/ui-library'; +import { CanvasV2Announcement } from 'features/notifications/components/canvasV2Announcement'; +import { NotificationIcon } from 'features/notifications/components/NotificationIcon'; +import InvokeSymbol from 'public/assets/images/invoke-favicon.png'; +import { useTranslation } from 'react-i18next'; + +export const Notifications = () => { + const { t } = useTranslation(); + return ( + + + + + + + + + + {t('whatsNew.whatsNewInInvoke')} + + + + + + + + ); +}; diff --git a/invokeai/frontend/web/src/features/ui/components/VerticalNavBar.tsx b/invokeai/frontend/web/src/features/ui/components/VerticalNavBar.tsx index f78aeb2f198..2864c28b1b1 100644 --- a/invokeai/frontend/web/src/features/ui/components/VerticalNavBar.tsx +++ b/invokeai/frontend/web/src/features/ui/components/VerticalNavBar.tsx @@ -10,6 +10,7 @@ import { useTranslation } from 'react-i18next'; import { PiBoundingBoxBold, PiCubeBold, PiFlowArrowBold, PiFrameCornersBold, PiQueueBold } from 'react-icons/pi'; import { TabButton } from './TabButton'; +import { Notifications } from './Notifications'; export const VerticalNavBar = memo(() => { const { t } = useTranslation(); @@ -37,6 +38,7 @@ export const VerticalNavBar = memo(() => { + {customNavComponent ? customNavComponent : } ); From 78016419f0bc76324c795bd6e737b579cd7f96ba Mon Sep 17 00:00:00 2001 From: Mary Hipp Date: Thu, 19 Sep 2024 16:01:33 -0400 Subject: [PATCH 2/8] more updates for new user experience --- invokeai/frontend/web/public/locales/en.json | 4 ++ .../ImageViewer/NoContentForViewer.tsx | 62 +++++++++---------- .../features/gallery/hooks/useHasImages.ts | 24 ++++--- 3 files changed, 51 insertions(+), 39 deletions(-) diff --git a/invokeai/frontend/web/public/locales/en.json b/invokeai/frontend/web/public/locales/en.json index 4165555e0ae..db1abf41ceb 100644 --- a/invokeai/frontend/web/public/locales/en.json +++ b/invokeai/frontend/web/public/locales/en.json @@ -2074,6 +2074,10 @@ }, "showSendingToAlerts": "Alert When Sending to Different View" }, + "newUserExperience": { + "toGetStarted": "To get started, enter a prompt in the box and click Invoke to generate your first image. You can choose to save your images directly to the Gallery or edit them to the Canvas", + "gettingStartedSeries": "Want more guidance? Check out our Getting Started Series for tips on unlocking the full potential of the Invoke Studio." + }, "whatsNew": { "whatsNewInInvoke": "What's New in Invoke", "canvasV2Announcement": { diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/NoContentForViewer.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/NoContentForViewer.tsx index ffac7863975..87e1f21daec 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/NoContentForViewer.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/NoContentForViewer.tsx @@ -1,46 +1,46 @@ import { Flex, Image, Text } from '@invoke-ai/ui-library'; import { useHasImages } from 'features/gallery/hooks/useHasImages'; import InvokeSymbol from 'public/assets/images/invoke-symbol-char-lrg.svg'; -import { useTranslation } from 'react-i18next'; +import { Trans, useTranslation } from 'react-i18next'; +import { PiImageBold } from 'react-icons/pi'; +import { IAINoContentFallback } from '../../../../common/components/IAIImageFallback'; export const NoContentForViewer = () => { const hasImages = useHasImages(); const { t } = useTranslation(); - // if (hasImages()) { - // return ; - // } + if (hasImages) { + return ; + } + return ( - - To get started, enter a prompt in the box and click{' '} - - Invoke - {' '} - to generate your first image. You can choose to save your images directly to the{' '} - - Gallery - {' '} - or edit them on the{' '} - - Canvas - - . + + , + }} + /> - - Want more guidance? Check out our{' '} - - Getting Started Series - {' '} - for tips on unlocking the full potential of the Invoke Studio. + + + + ), + }} + /> ); diff --git a/invokeai/frontend/web/src/features/gallery/hooks/useHasImages.ts b/invokeai/frontend/web/src/features/gallery/hooks/useHasImages.ts index 97e9104a861..3680f6e89dc 100644 --- a/invokeai/frontend/web/src/features/gallery/hooks/useHasImages.ts +++ b/invokeai/frontend/web/src/features/gallery/hooks/useHasImages.ts @@ -1,19 +1,27 @@ -import { selectListImagesQueryArgs } from "features/gallery/store/gallerySelectors"; -import { useCallback } from "react" +import { useMemo } from "react" import { useListAllBoardsQuery } from "services/api/endpoints/boards"; import { useListImagesQuery } from "services/api/endpoints/images"; export const useHasImages = () => { const { data: boardList } = useListAllBoardsQuery({ include_archived: true }) - const { data: uncategorizedImages } = useListImagesQuery({ ...selectListImagesQueryArgs, board_id: "none" }) + const { data: uncategorizedImages } = useListImagesQuery({ + board_id: "none", + offset: 0, + limit: 0, + is_intermediate: false, + }) - const hasImages = useCallback(() => { + const hasImages = useMemo(() => { const hasBoards = boardList && boardList.length > 0; - if (!hasBoards) { - return uncategorizedImages ? uncategorizedImages.total > 0 : false - } else { - return boardList.filter(board => board.image_count > 0).length > 0 + + if (hasBoards) { + if (boardList.filter(board => board.image_count > 0).length > 0) { + return true + } } + return uncategorizedImages ? uncategorizedImages.total > 0 : false + + }, [boardList, uncategorizedImages]) return hasImages From 5f5013f14166416962a191ddea81f053ce75924b Mon Sep 17 00:00:00 2001 From: Mary Hipp Date: Thu, 19 Sep 2024 16:26:54 -0400 Subject: [PATCH 3/8] pull whats new out --- invokeai/frontend/web/public/locales/en.json | 11 ----- .../ImageViewer/CurrentImagePreview.tsx | 9 +--- .../ImageViewer/NoContentForViewer.tsx | 2 +- .../gallery/hooks/useGalleryNavigation.ts | 30 ++++++++++-- .../features/gallery/hooks/useHasImages.ts | 13 +++-- .../components/NotificationIcon.tsx | 18 ------- .../components/canvasV2Announcement.tsx | 34 -------------- .../features/ui/components/Notifications.tsx | 37 --------------- .../features/ui/components/VerticalNavBar.tsx | 47 ------------------- 9 files changed, 36 insertions(+), 165 deletions(-) delete mode 100644 invokeai/frontend/web/src/features/notifications/components/NotificationIcon.tsx delete mode 100644 invokeai/frontend/web/src/features/notifications/components/canvasV2Announcement.tsx delete mode 100644 invokeai/frontend/web/src/features/ui/components/Notifications.tsx delete mode 100644 invokeai/frontend/web/src/features/ui/components/VerticalNavBar.tsx diff --git a/invokeai/frontend/web/public/locales/en.json b/invokeai/frontend/web/public/locales/en.json index db1abf41ceb..6c389d88cea 100644 --- a/invokeai/frontend/web/public/locales/en.json +++ b/invokeai/frontend/web/public/locales/en.json @@ -2077,16 +2077,5 @@ "newUserExperience": { "toGetStarted": "To get started, enter a prompt in the box and click Invoke to generate your first image. You can choose to save your images directly to the Gallery or edit them to the Canvas", "gettingStartedSeries": "Want more guidance? Check out our Getting Started Series for tips on unlocking the full potential of the Invoke Studio." - }, - "whatsNew": { - "whatsNewInInvoke": "What's New in Invoke", - "canvasV2Announcement": { - "newCanvas": "A powerful new control canvas", - "newLayerTypes": "New layer types for even more control", - "fluxSupport": "Support for the Flux family of models", - "readReleaseNotes": "Read Release Notes", - "watchReleaseVideo": "Watch Release Video", - "watchUiUpdatesOverview": "Watch UI Updates Overview" - } } } diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/CurrentImagePreview.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/CurrentImagePreview.tsx index 52b2b49d258..6a008fc86a0 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/CurrentImagePreview.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/CurrentImagePreview.tsx @@ -3,7 +3,6 @@ import { useStore } from '@nanostores/react'; import { skipToken } from '@reduxjs/toolkit/query'; import { useAppSelector } from 'app/store/storeHooks'; import IAIDndImage from 'common/components/IAIDndImage'; -import { IAINoContentFallback } from 'common/components/IAIImageFallback'; import type { TypesafeDraggableData } from 'features/dnd/types'; import ImageMetadataViewer from 'features/gallery/components/ImageMetadataViewer/ImageMetadataViewer'; import NextPrevImageButtons from 'features/gallery/components/NextPrevImageButtons'; @@ -12,25 +11,19 @@ import { selectShouldShowImageDetails, selectShouldShowProgressInViewer } from ' import type { AnimationProps } from 'framer-motion'; import { AnimatePresence, motion } from 'framer-motion'; import { memo, useCallback, useMemo, useRef, useState } from 'react'; -import { useTranslation } from 'react-i18next'; -import { PiImageBold } from 'react-icons/pi'; import { useGetImageDTOQuery } from 'services/api/endpoints/images'; import { $hasProgress, $isProgressFromCanvas } from 'services/events/stores'; -import ProgressImage from './ProgressImage'; -import { useHasImages } from '../../hooks/useHasImages'; import { NoContentForViewer } from './NoContentForViewer'; +import ProgressImage from './ProgressImage'; const CurrentImagePreview = () => { - const { t } = useTranslation(); const shouldShowImageDetails = useAppSelector(selectShouldShowImageDetails); const imageName = useAppSelector(selectLastSelectedImageName); const hasDenoiseProgress = useStore($hasProgress); const isProgressFromCanvas = useStore($isProgressFromCanvas); const shouldShowProgressInViewer = useAppSelector(selectShouldShowProgressInViewer); - const hasImages = useHasImages(); - const { currentData: imageDTO } = useGetImageDTOQuery(imageName ?? skipToken); const draggableData = useMemo(() => { diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/NoContentForViewer.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/NoContentForViewer.tsx index 87e1f21daec..17c2a7c5dbf 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/NoContentForViewer.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/NoContentForViewer.tsx @@ -1,9 +1,9 @@ import { Flex, Image, Text } from '@invoke-ai/ui-library'; +import { IAINoContentFallback } from 'common/components/IAIImageFallback'; import { useHasImages } from 'features/gallery/hooks/useHasImages'; import InvokeSymbol from 'public/assets/images/invoke-symbol-char-lrg.svg'; import { Trans, useTranslation } from 'react-i18next'; import { PiImageBold } from 'react-icons/pi'; -import { IAINoContentFallback } from '../../../../common/components/IAIImageFallback'; export const NoContentForViewer = () => { const hasImages = useHasImages(); diff --git a/invokeai/frontend/web/src/features/gallery/hooks/useGalleryNavigation.ts b/invokeai/frontend/web/src/features/gallery/hooks/useGalleryNavigation.ts index 1e98123e38d..7e699aff979 100644 --- a/invokeai/frontend/web/src/features/gallery/hooks/useGalleryNavigation.ts +++ b/invokeai/frontend/web/src/features/gallery/hooks/useGalleryNavigation.ts @@ -29,13 +29,33 @@ import type { ImageDTO } from 'services/api/types'; * Gets the number of images per row in the gallery by grabbing their DOM elements. */ const getImagesPerRow = (): number => { - const widthOfGalleryImage = - document.querySelector(`.${GALLERY_IMAGE_CLASS_NAME}`)?.getBoundingClientRect().width ?? 1; + const imageEl = document.querySelector(`.${GALLERY_IMAGE_CLASS_NAME}`); + const gridEl = document.querySelector(`.${GALLERY_GRID_CLASS_NAME}`); - const widthOfGalleryGrid = document.querySelector(`.${GALLERY_GRID_CLASS_NAME}`)?.getBoundingClientRect().width ?? 0; + if (!imageEl || !gridEl) { + return 0; + } + + const container = gridEl.parentElement; + if (!container) { + return 0; + } + + const imageRect = imageEl.getBoundingClientRect(); + const containerRect = container.getBoundingClientRect(); + + const gridElStyle = window.getComputedStyle(gridEl); + const gap = parseFloat(gridElStyle.gap); + + // Validate input values + if (imageRect.width <= 0 || gap < 0) { + return 0; + } + + // Calculate maximum number of images per row + const imagesPerRow = Math.floor((containerRect.width + 1) / (imageRect.width + gap)); - const imagesPerRow = Math.round(widthOfGalleryGrid / widthOfGalleryImage); - return imagesPerRow + return imagesPerRow; }; /** diff --git a/invokeai/frontend/web/src/features/gallery/hooks/useHasImages.ts b/invokeai/frontend/web/src/features/gallery/hooks/useHasImages.ts index 3680f6e89dc..e3ecd7aa63f 100644 --- a/invokeai/frontend/web/src/features/gallery/hooks/useHasImages.ts +++ b/invokeai/frontend/web/src/features/gallery/hooks/useHasImages.ts @@ -3,8 +3,8 @@ import { useListAllBoardsQuery } from "services/api/endpoints/boards"; import { useListImagesQuery } from "services/api/endpoints/images"; export const useHasImages = () => { - const { data: boardList } = useListAllBoardsQuery({ include_archived: true }) - const { data: uncategorizedImages } = useListImagesQuery({ + const { data: boardList, isLoading: loadingBoards } = useListAllBoardsQuery({ include_archived: true }) + const { data: uncategorizedImages, isLoading: loadingImages } = useListImagesQuery({ board_id: "none", offset: 0, limit: 0, @@ -12,6 +12,11 @@ export const useHasImages = () => { }) const hasImages = useMemo(() => { + // default to true + if (loadingBoards || loadingImages) { + return true + } + const hasBoards = boardList && boardList.length > 0; if (hasBoards) { @@ -19,10 +24,10 @@ export const useHasImages = () => { return true } } - return uncategorizedImages ? uncategorizedImages.total > 0 : false + return uncategorizedImages ? uncategorizedImages.total > 0 : true - }, [boardList, uncategorizedImages]) + }, [boardList, uncategorizedImages, loadingBoards, loadingImages]) return hasImages } \ No newline at end of file diff --git a/invokeai/frontend/web/src/features/notifications/components/NotificationIcon.tsx b/invokeai/frontend/web/src/features/notifications/components/NotificationIcon.tsx deleted file mode 100644 index 58cf4c150b1..00000000000 --- a/invokeai/frontend/web/src/features/notifications/components/NotificationIcon.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { Box,Flex, IconButton } from '@invoke-ai/ui-library'; -import { PiLightbulbFilamentBold } from 'react-icons/pi'; - -export const NotificationIcon = ({ showIndicator }: { showIndicator: boolean }) => { - return ( - - } - boxSize={8} - /> - {showIndicator && ( - - )} - - ); -}; diff --git a/invokeai/frontend/web/src/features/notifications/components/canvasV2Announcement.tsx b/invokeai/frontend/web/src/features/notifications/components/canvasV2Announcement.tsx deleted file mode 100644 index 2b07979d67e..00000000000 --- a/invokeai/frontend/web/src/features/notifications/components/canvasV2Announcement.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import { Flex, Icon, ListItem, Text,UnorderedList } from '@invoke-ai/ui-library'; -import { PiArrowSquareOutBold } from 'react-icons/pi'; - -export const CanvasV2Announcement = () => { - return ( - - - A poweful new control canvas - New layer types for even more control - Support for the Flux family of models - - - - - Read Release Notes - - - - - - Watch Release Video - - - - - - Watch UI Updates Overview - - - - - - ); -}; diff --git a/invokeai/frontend/web/src/features/ui/components/Notifications.tsx b/invokeai/frontend/web/src/features/ui/components/Notifications.tsx deleted file mode 100644 index 77ff2858ece..00000000000 --- a/invokeai/frontend/web/src/features/ui/components/Notifications.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import { - Flex, - Image, - Popover, - PopoverBody, - PopoverCloseButton, - PopoverContent, - PopoverHeader, - PopoverTrigger, -} from '@invoke-ai/ui-library'; -import { CanvasV2Announcement } from 'features/notifications/components/canvasV2Announcement'; -import { NotificationIcon } from 'features/notifications/components/NotificationIcon'; -import InvokeSymbol from 'public/assets/images/invoke-favicon.png'; -import { useTranslation } from 'react-i18next'; - -export const Notifications = () => { - const { t } = useTranslation(); - return ( - - - - - - - - - - {t('whatsNew.whatsNewInInvoke')} - - - - - - - - ); -}; diff --git a/invokeai/frontend/web/src/features/ui/components/VerticalNavBar.tsx b/invokeai/frontend/web/src/features/ui/components/VerticalNavBar.tsx deleted file mode 100644 index 2864c28b1b1..00000000000 --- a/invokeai/frontend/web/src/features/ui/components/VerticalNavBar.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import { Flex, Spacer } from '@invoke-ai/ui-library'; -import { useStore } from '@nanostores/react'; -import { $customNavComponent } from 'app/store/nanostores/customNavComponent'; -import InvokeAILogoComponent from 'features/system/components/InvokeAILogoComponent'; -import SettingsMenu from 'features/system/components/SettingsModal/SettingsMenu'; -import StatusIndicator from 'features/system/components/StatusIndicator'; -import { TabMountGate } from 'features/ui/components/TabMountGate'; -import { memo } from 'react'; -import { useTranslation } from 'react-i18next'; -import { PiBoundingBoxBold, PiCubeBold, PiFlowArrowBold, PiFrameCornersBold, PiQueueBold } from 'react-icons/pi'; - -import { TabButton } from './TabButton'; -import { Notifications } from './Notifications'; - -export const VerticalNavBar = memo(() => { - const { t } = useTranslation(); - const customNavComponent = useStore($customNavComponent); - - return ( - - - - - } label={t('ui.tabs.canvas')} /> - - - } label={t('ui.tabs.upscaling')} /> - - - } label={t('ui.tabs.workflows')} /> - - - } label={t('ui.tabs.models')} /> - - - } label={t('ui.tabs.queue')} /> - - - - - - {customNavComponent ? customNavComponent : } - - ); -}); - -VerticalNavBar.displayName = 'VerticalNavBar'; From 0434b99d47c6b3812b81876c954ae6bf0dd7b53b Mon Sep 17 00:00:00 2001 From: Mary Hipp Date: Thu, 19 Sep 2024 19:13:08 -0400 Subject: [PATCH 4/8] use loading state --- .../gallery/components/ImageViewer/NoContentForViewer.tsx | 7 ++++++- .../web/src/features/gallery/hooks/useGalleryNavigation.ts | 5 +---- .../web/src/features/gallery/hooks/useHasImages.ts | 5 ++++- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/NoContentForViewer.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/NoContentForViewer.tsx index 17c2a7c5dbf..78873bdfa61 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/NoContentForViewer.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/NoContentForViewer.tsx @@ -1,14 +1,19 @@ import { Flex, Image, Text } from '@invoke-ai/ui-library'; import { IAINoContentFallback } from 'common/components/IAIImageFallback'; -import { useHasImages } from 'features/gallery/hooks/useHasImages'; +import { LOADING_SYMBOL, useHasImages } from 'features/gallery/hooks/useHasImages'; import InvokeSymbol from 'public/assets/images/invoke-symbol-char-lrg.svg'; import { Trans, useTranslation } from 'react-i18next'; import { PiImageBold } from 'react-icons/pi'; +import Loading from '../../../../common/components/Loading/Loading'; export const NoContentForViewer = () => { const hasImages = useHasImages(); const { t } = useTranslation(); + if (hasImages === LOADING_SYMBOL) { + return ; + } + if (hasImages) { return ; } diff --git a/invokeai/frontend/web/src/features/gallery/hooks/useGalleryNavigation.ts b/invokeai/frontend/web/src/features/gallery/hooks/useGalleryNavigation.ts index 6da1a367602..fddd3fd6ea0 100644 --- a/invokeai/frontend/web/src/features/gallery/hooks/useGalleryNavigation.ts +++ b/invokeai/frontend/web/src/features/gallery/hooks/useGalleryNavigation.ts @@ -35,7 +35,6 @@ const getImagesPerRow = (): number => { if (!imageEl || !gridEl) { return 0; } - const container = gridEl.parentElement; if (!container) { return 0; @@ -44,6 +43,7 @@ const getImagesPerRow = (): number => { const imageRect = imageEl.getBoundingClientRect(); const containerRect = container.getBoundingClientRect(); + // We need to account for the gap between images const gridElStyle = window.getComputedStyle(gridEl); const gap = parseFloat(gridElStyle.gap); @@ -65,9 +65,6 @@ const getImagesPerRow = (): number => { } } - // Calculate maximum number of images per row - const imagesPerRow = Math.floor((containerRect.width + 1) / (imageRect.width + gap)); - return imagesPerRow; }; diff --git a/invokeai/frontend/web/src/features/gallery/hooks/useHasImages.ts b/invokeai/frontend/web/src/features/gallery/hooks/useHasImages.ts index e3ecd7aa63f..f875a4c78f2 100644 --- a/invokeai/frontend/web/src/features/gallery/hooks/useHasImages.ts +++ b/invokeai/frontend/web/src/features/gallery/hooks/useHasImages.ts @@ -2,6 +2,9 @@ import { useMemo } from "react" import { useListAllBoardsQuery } from "services/api/endpoints/boards"; import { useListImagesQuery } from "services/api/endpoints/images"; +export const LOADING_SYMBOL = Symbol("LOADING") + + export const useHasImages = () => { const { data: boardList, isLoading: loadingBoards } = useListAllBoardsQuery({ include_archived: true }) const { data: uncategorizedImages, isLoading: loadingImages } = useListImagesQuery({ @@ -14,7 +17,7 @@ export const useHasImages = () => { const hasImages = useMemo(() => { // default to true if (loadingBoards || loadingImages) { - return true + return LOADING_SYMBOL } const hasBoards = boardList && boardList.length > 0; From 90559b7f76fe96e80cdb53ff147ca0804f627a63 Mon Sep 17 00:00:00 2001 From: Mary Hipp Date: Thu, 19 Sep 2024 19:14:02 -0400 Subject: [PATCH 5/8] lint --- .../ImageViewer/NoContentForViewer.tsx | 2 +- .../features/gallery/hooks/useHasImages.ts | 63 +++++++++---------- 2 files changed, 31 insertions(+), 34 deletions(-) diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/NoContentForViewer.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/NoContentForViewer.tsx index 78873bdfa61..171acbc8f04 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/NoContentForViewer.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/NoContentForViewer.tsx @@ -1,10 +1,10 @@ import { Flex, Image, Text } from '@invoke-ai/ui-library'; import { IAINoContentFallback } from 'common/components/IAIImageFallback'; +import Loading from 'common/components/Loading/Loading'; import { LOADING_SYMBOL, useHasImages } from 'features/gallery/hooks/useHasImages'; import InvokeSymbol from 'public/assets/images/invoke-symbol-char-lrg.svg'; import { Trans, useTranslation } from 'react-i18next'; import { PiImageBold } from 'react-icons/pi'; -import Loading from '../../../../common/components/Loading/Loading'; export const NoContentForViewer = () => { const hasImages = useHasImages(); diff --git a/invokeai/frontend/web/src/features/gallery/hooks/useHasImages.ts b/invokeai/frontend/web/src/features/gallery/hooks/useHasImages.ts index f875a4c78f2..bf94c465b95 100644 --- a/invokeai/frontend/web/src/features/gallery/hooks/useHasImages.ts +++ b/invokeai/frontend/web/src/features/gallery/hooks/useHasImages.ts @@ -1,36 +1,33 @@ -import { useMemo } from "react" -import { useListAllBoardsQuery } from "services/api/endpoints/boards"; -import { useListImagesQuery } from "services/api/endpoints/images"; - -export const LOADING_SYMBOL = Symbol("LOADING") +import { useMemo } from 'react'; +import { useListAllBoardsQuery } from 'services/api/endpoints/boards'; +import { useListImagesQuery } from 'services/api/endpoints/images'; +export const LOADING_SYMBOL = Symbol('LOADING'); export const useHasImages = () => { - const { data: boardList, isLoading: loadingBoards } = useListAllBoardsQuery({ include_archived: true }) - const { data: uncategorizedImages, isLoading: loadingImages } = useListImagesQuery({ - board_id: "none", - offset: 0, - limit: 0, - is_intermediate: false, - }) - - const hasImages = useMemo(() => { - // default to true - if (loadingBoards || loadingImages) { - return LOADING_SYMBOL - } - - const hasBoards = boardList && boardList.length > 0; - - if (hasBoards) { - if (boardList.filter(board => board.image_count > 0).length > 0) { - return true - } - } - return uncategorizedImages ? uncategorizedImages.total > 0 : true - - - }, [boardList, uncategorizedImages, loadingBoards, loadingImages]) - - return hasImages -} \ No newline at end of file + const { data: boardList, isLoading: loadingBoards } = useListAllBoardsQuery({ include_archived: true }); + const { data: uncategorizedImages, isLoading: loadingImages } = useListImagesQuery({ + board_id: 'none', + offset: 0, + limit: 0, + is_intermediate: false, + }); + + const hasImages = useMemo(() => { + // default to true + if (loadingBoards || loadingImages) { + return LOADING_SYMBOL; + } + + const hasBoards = boardList && boardList.length > 0; + + if (hasBoards) { + if (boardList.filter((board) => board.image_count > 0).length > 0) { + return true; + } + } + return uncategorizedImages ? uncategorizedImages.total > 0 : true; + }, [boardList, uncategorizedImages, loadingBoards, loadingImages]); + + return hasImages; +}; From 82bd293489253b6174b98368c183cccab0e0440f Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Fri, 20 Sep 2024 10:45:08 +1000 Subject: [PATCH 6/8] fix(ui): translation missing period --- invokeai/frontend/web/public/locales/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/invokeai/frontend/web/public/locales/en.json b/invokeai/frontend/web/public/locales/en.json index db1abf41ceb..953456eb92b 100644 --- a/invokeai/frontend/web/public/locales/en.json +++ b/invokeai/frontend/web/public/locales/en.json @@ -2075,7 +2075,7 @@ "showSendingToAlerts": "Alert When Sending to Different View" }, "newUserExperience": { - "toGetStarted": "To get started, enter a prompt in the box and click Invoke to generate your first image. You can choose to save your images directly to the Gallery or edit them to the Canvas", + "toGetStarted": "To get started, enter a prompt in the box and click Invoke to generate your first image. You can choose to save your images directly to the Gallery or edit them to the Canvas.", "gettingStartedSeries": "Want more guidance? Check out our Getting Started Series for tips on unlocking the full potential of the Invoke Studio." }, "whatsNew": { From 358f23b64aa26416a24fc9f08ae7f9adc3d4f999 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Fri, 20 Sep 2024 10:54:48 +1000 Subject: [PATCH 7/8] feat(ui): create icon component for invoke logo --- .../web/src/common/components/InvokeLogoIcon.tsx | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 invokeai/frontend/web/src/common/components/InvokeLogoIcon.tsx diff --git a/invokeai/frontend/web/src/common/components/InvokeLogoIcon.tsx b/invokeai/frontend/web/src/common/components/InvokeLogoIcon.tsx new file mode 100644 index 00000000000..e744e1a5c85 --- /dev/null +++ b/invokeai/frontend/web/src/common/components/InvokeLogoIcon.tsx @@ -0,0 +1,13 @@ +import type { IconProps } from '@invoke-ai/ui-library'; +import { Icon } from '@invoke-ai/ui-library'; +import { memo } from 'react'; + +export const InvokeLogoIcon = memo((props: IconProps) => { + return ( + + + + ); +}); + +InvokeLogoIcon.displayName = 'InvokeLogoIcon'; From 4049f0bfa53c2b58b179e1ab19f5e6d3458a82c1 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Fri, 20 Sep 2024 11:01:02 +1000 Subject: [PATCH 8/8] feat(ui): tweaked invoke logo colors --- .../ImageViewer/NoContentForViewer.tsx | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/NoContentForViewer.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/NoContentForViewer.tsx index 171acbc8f04..fef3ddac6de 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageViewer/NoContentForViewer.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageViewer/NoContentForViewer.tsx @@ -1,8 +1,7 @@ -import { Flex, Image, Text } from '@invoke-ai/ui-library'; +import { Flex, Spinner, Text } from '@invoke-ai/ui-library'; import { IAINoContentFallback } from 'common/components/IAIImageFallback'; -import Loading from 'common/components/Loading/Loading'; +import { InvokeLogoIcon } from 'common/components/InvokeLogoIcon'; import { LOADING_SYMBOL, useHasImages } from 'features/gallery/hooks/useHasImages'; -import InvokeSymbol from 'public/assets/images/invoke-symbol-char-lrg.svg'; import { Trans, useTranslation } from 'react-i18next'; import { PiImageBold } from 'react-icons/pi'; @@ -11,7 +10,15 @@ export const NoContentForViewer = () => { const { t } = useTranslation(); if (hasImages === LOADING_SYMBOL) { - return ; + return ( + // Blank bg w/ a spinner. The new user experience components below have an invoke logo, but it's not centered. + // If we show the logo while loading, there is an awkward layout shift where the invoke logo moves a bit. Less + // jarring to show a blank bg with a spinner - it will only be shown for a moment as we do the initial images + // fetching. + + + + ); } if (hasImages) { @@ -20,8 +27,8 @@ export const NoContentForViewer = () => { return ( - - + +