feat(ui): new user experience (#6892)

* wip

* more updates for new user experience

* pull whats new out

* use loading state

* lint

* fix(ui): translation missing period

* feat(ui): create icon component for invoke logo

* feat(ui): tweaked invoke logo colors

---------

Co-authored-by: Mary Hipp <maryhipp@Marys-MacBook-Air.local>
Co-authored-by: psychedelicious <4822129+psychedelicious@users.noreply.github.com>
This commit is contained in:
Mary Hipp Rogers
2024-09-19 21:06:18 -04:00
committed by GitHub
parent c79d9453dd
commit 674e5eb4e5
5 changed files with 111 additions and 5 deletions

View File

@@ -2074,6 +2074,10 @@
},
"showSendingToAlerts": "Alert When Sending to Different View"
},
"newUserExperience": {
"toGetStarted": "To get started, enter a prompt in the box and click <StrongComponent>Invoke</StrongComponent> to generate your first image. You can choose to save your images directly to the <StrongComponent>Gallery</StrongComponent> or edit them to the <StrongComponent>Canvas</StrongComponent>.",
"gettingStartedSeries": "Want more guidance? Check out our <LinkComponent>Getting Started Series</LinkComponent> for tips on unlocking the full potential of the Invoke Studio."
},
"whatsNew": {
"whatsNewInInvoke": "What's New in Invoke",
"canvasV2Announcement": {

View File

@@ -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 (
<Icon boxSize={8} opacity={1} stroke="base.500" viewBox="0 0 66 66" fill="none" {...props}>
<path d="M43.9137 16H63.1211V3H3.12109V16H22.3285L43.9137 50H63.1211V63H3.12109V50H22.3285" strokeWidth="5" />
</Icon>
);
});
InvokeLogoIcon.displayName = 'InvokeLogoIcon';

View File

@@ -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,15 +11,13 @@ 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 { NoContentForViewer } from './NoContentForViewer';
import ProgressImage from './ProgressImage';
const CurrentImagePreview = () => {
const { t } = useTranslation();
const shouldShowImageDetails = useAppSelector(selectShouldShowImageDetails);
const imageName = useAppSelector(selectLastSelectedImageName);
const hasDenoiseProgress = useStore($hasProgress);
@@ -72,7 +69,7 @@ const CurrentImagePreview = () => {
isUploadDisabled={true}
fitContainer
useThumbailFallback
noContentFallback={<IAINoContentFallback icon={PiImageBold} label={t('gallery.noImageSelected')} />}
noContentFallback={<NoContentForViewer />}
dataTestId="image-preview"
/>
)}

View File

@@ -0,0 +1,59 @@
import { Flex, Spinner, Text } from '@invoke-ai/ui-library';
import { IAINoContentFallback } from 'common/components/IAIImageFallback';
import { InvokeLogoIcon } from 'common/components/InvokeLogoIcon';
import { LOADING_SYMBOL, useHasImages } from 'features/gallery/hooks/useHasImages';
import { Trans, useTranslation } from 'react-i18next';
import { PiImageBold } from 'react-icons/pi';
export const NoContentForViewer = () => {
const hasImages = useHasImages();
const { t } = useTranslation();
if (hasImages === LOADING_SYMBOL) {
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.
<Flex position="relative" width="full" height="full" alignItems="center" justifyContent="center">
<Spinner label="Loading" color="grey" position="absolute" size="sm" width={8} height={8} right={4} bottom={4} />
</Flex>
);
}
if (hasImages) {
return <IAINoContentFallback icon={PiImageBold} label={t('gallery.noImageSelected')} />;
}
return (
<Flex flexDir="column" gap={4} alignItems="center" textAlign="center" maxW="600px">
<InvokeLogoIcon w={40} h={40} />
<Text fontSize="md" color="base.200" pt={16}>
<Trans
i18nKey="newUserExperience.toGetStarted"
components={{
StrongComponent: <Text as="span" color="white" fontSize="md" fontWeight="semibold" />,
}}
/>
</Text>
<Text fontSize="md" color="base.200">
<Trans
i18nKey="newUserExperience.gettingStartedSeries"
components={{
LinkComponent: (
<Text
as="a"
color="white"
fontSize="md"
fontWeight="semibold"
href="https://www.youtube.com/@invokeai/videos"
target="_blank"
/>
),
}}
/>
</Text>
</Flex>
);
};

View File

@@ -0,0 +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');
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;
};