diff --git a/invokeai/frontend/web/package.json b/invokeai/frontend/web/package.json index b8cd3e02000..285356cb6e2 100644 --- a/invokeai/frontend/web/package.json +++ b/invokeai/frontend/web/package.json @@ -58,7 +58,7 @@ "@dagrejs/dagre": "^1.1.4", "@dagrejs/graphlib": "^2.2.4", "@fontsource-variable/inter": "^5.1.0", - "@invoke-ai/ui-library": "^0.0.43", + "@invoke-ai/ui-library": "^0.0.44", "@nanostores/react": "^0.7.3", "@reduxjs/toolkit": "2.2.3", "@roarr/browser-log-writer": "^1.3.0", diff --git a/invokeai/frontend/web/pnpm-lock.yaml b/invokeai/frontend/web/pnpm-lock.yaml index 17ead7a8e59..2251219af39 100644 --- a/invokeai/frontend/web/pnpm-lock.yaml +++ b/invokeai/frontend/web/pnpm-lock.yaml @@ -24,8 +24,8 @@ dependencies: specifier: ^5.1.0 version: 5.1.0 '@invoke-ai/ui-library': - specifier: ^0.0.43 - version: 0.0.43(@chakra-ui/form-control@2.2.0)(@chakra-ui/icon@3.2.0)(@chakra-ui/media-query@3.3.0)(@chakra-ui/menu@2.2.1)(@chakra-ui/spinner@2.1.0)(@chakra-ui/system@2.6.2)(@fontsource-variable/inter@5.1.0)(@types/react@18.3.11)(i18next@23.15.1)(react-dom@18.3.1)(react@18.3.1) + specifier: ^0.0.44 + version: 0.0.44(@chakra-ui/form-control@2.2.0)(@chakra-ui/icon@3.2.0)(@chakra-ui/media-query@3.3.0)(@chakra-ui/menu@2.2.1)(@chakra-ui/spinner@2.1.0)(@chakra-ui/system@2.6.2)(@fontsource-variable/inter@5.1.0)(@types/react@18.3.11)(i18next@23.15.1)(react-dom@18.3.1)(react@18.3.1) '@nanostores/react': specifier: ^0.7.3 version: 0.7.3(nanostores@0.11.3)(react@18.3.1) @@ -515,8 +515,8 @@ packages: resolution: {integrity: sha512-MV6D4VLRIHr4PkW4zMyqfrNS1mPlCTiCXwvYGtDFQYr+xHFfonhAuf9WjsSc0nyp2m0OdkSLnzmVKkZFLo25Tg==} dev: false - /@chakra-ui/anatomy@2.3.4: - resolution: {integrity: sha512-fFIYN7L276gw0Q7/ikMMlZxP7mvnjRaWJ7f3Jsf9VtDOi6eAYIBRrhQe6+SZ0PGmoOkRaBc7gSE5oeIbgFFyrw==} + /@chakra-ui/anatomy@2.3.5: + resolution: {integrity: sha512-3im33cUOxCbISjaBlINE2u8BOwJSCdzpjCX0H+0JxK2xz26UaVA5xeI3NYHUoxDnr/QIrgfrllGxS0szYwOcyg==} dev: false /@chakra-ui/breakpoint-utils@2.0.8: @@ -573,12 +573,12 @@ packages: react: 18.3.1 dev: false - /@chakra-ui/hooks@2.4.2(react@18.3.1): - resolution: {integrity: sha512-LRKiVE1oA7afT5tbbSKAy7Uas2xFHE6IkrQdbhWCHmkHBUtPvjQQDgwtnd4IRZPmoEfNGwoJ/MQpwOM/NRTTwA==} + /@chakra-ui/hooks@2.4.3(react@18.3.1): + resolution: {integrity: sha512-Sr2zsoTZw3p7HbrUy4aLpTIkE2XXUelAUgg3NGwMzrmx75bE0qVyiuuTFOuyEzGxYVV2Fe8QtcKKilm6RwzTGg==} peerDependencies: react: '>=18' dependencies: - '@chakra-ui/utils': 2.2.2(react@18.3.1) + '@chakra-ui/utils': 2.2.3(react@18.3.1) '@zag-js/element-size': 0.31.1 copy-to-clipboard: 3.3.3 framesync: 6.1.2 @@ -596,13 +596,13 @@ packages: react: 18.3.1 dev: false - /@chakra-ui/icons@2.2.4(@chakra-ui/react@2.10.2)(react@18.3.1): + /@chakra-ui/icons@2.2.4(@chakra-ui/react@2.10.4)(react@18.3.1): resolution: {integrity: sha512-l5QdBgwrAg3Sc2BRqtNkJpfuLw/pWRDwwT58J6c4PqQT6wzXxyNa8Q0PForu1ltB5qEiFb1kxr/F/HO1EwNa6g==} peerDependencies: '@chakra-ui/react': '>=2.0.0' react: '>=18' dependencies: - '@chakra-ui/react': 2.10.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(@types/react@18.3.11)(framer-motion@11.10.0)(react-dom@18.3.1)(react@18.3.1) + '@chakra-ui/react': 2.10.4(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(@types/react@18.3.11)(framer-motion@11.10.0)(react-dom@18.3.1)(react@18.3.1) react: 18.3.1 dev: false @@ -825,8 +825,8 @@ packages: react: 18.3.1 dev: false - /@chakra-ui/react@2.10.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(@types/react@18.3.11)(framer-motion@11.10.0)(react-dom@18.3.1)(react@18.3.1): - resolution: {integrity: sha512-TfIHTqTlxTHYJZBtpiR5EZasPUrLYKJxdbHkdOJb5G1OQ+2c5kKl5XA7c2pMtsEptzb7KxAAIB62t3hxdfWp1w==} + /@chakra-ui/react@2.10.4(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(@types/react@18.3.11)(framer-motion@11.10.0)(react-dom@18.3.1)(react@18.3.1): + resolution: {integrity: sha512-XyRWnuZ1Uw7Mlj5pKUGO5/WhnIHP/EOrpy6lGZC1yWlkd0eIfIpYMZ1ALTZx4KPEdbBaes48dgiMT2ROCqLhkA==} peerDependencies: '@emotion/react': '>=11' '@emotion/styled': '>=11' @@ -834,10 +834,10 @@ packages: react: '>=18' react-dom: '>=18' dependencies: - '@chakra-ui/hooks': 2.4.2(react@18.3.1) - '@chakra-ui/styled-system': 2.11.2(react@18.3.1) - '@chakra-ui/theme': 3.4.6(@chakra-ui/styled-system@2.11.2)(react@18.3.1) - '@chakra-ui/utils': 2.2.2(react@18.3.1) + '@chakra-ui/hooks': 2.4.3(react@18.3.1) + '@chakra-ui/styled-system': 2.12.1(react@18.3.1) + '@chakra-ui/theme': 3.4.7(@chakra-ui/styled-system@2.12.1)(react@18.3.1) + '@chakra-ui/utils': 2.2.3(react@18.3.1) '@emotion/react': 11.13.3(@types/react@18.3.11)(react@18.3.1) '@emotion/styled': 11.13.0(@emotion/react@11.13.3)(@types/react@18.3.11)(react@18.3.1) '@popperjs/core': 2.11.8 @@ -868,10 +868,10 @@ packages: react: 18.3.1 dev: false - /@chakra-ui/styled-system@2.11.2(react@18.3.1): - resolution: {integrity: sha512-y++z2Uop+hjfZX9mbH88F1ikazPv32asD2er56zMJBemUAzweXnHTpiCQbluEDSUDhqmghVZAdb+5L4XLbsRxA==} + /@chakra-ui/styled-system@2.12.1(react@18.3.1): + resolution: {integrity: sha512-DQph1nDiCPtgze7nDe0a36530ByXb5VpPosKGyWMvKocVeZJcDtYG6XM0+V5a0wKuFBXsViBBRIFUTiUesJAcg==} dependencies: - '@chakra-ui/utils': 2.2.2(react@18.3.1) + '@chakra-ui/utils': 2.2.3(react@18.3.1) csstype: 3.1.3 transitivePeerDependencies: - react @@ -915,14 +915,14 @@ packages: color2k: 2.0.3 dev: false - /@chakra-ui/theme-tools@2.2.6(@chakra-ui/styled-system@2.11.2)(react@18.3.1): - resolution: {integrity: sha512-3UhKPyzKbV3l/bg1iQN9PBvffYp+EBOoYMUaeTUdieQRPFzo2jbYR0lNCxqv8h5aGM/k54nCHU2M/GStyi9F2A==} + /@chakra-ui/theme-tools@2.2.7(@chakra-ui/styled-system@2.12.1)(react@18.3.1): + resolution: {integrity: sha512-K/VJd0QcnKik7m+qZTkggqNLep6+MPUu8IP5TUpHsnSM5R/RVjsJIR7gO8IZVAIMIGLLTIhGshHxeMekqv6LcQ==} peerDependencies: '@chakra-ui/styled-system': '>=2.0.0' dependencies: - '@chakra-ui/anatomy': 2.3.4 - '@chakra-ui/styled-system': 2.11.2(react@18.3.1) - '@chakra-ui/utils': 2.2.2(react@18.3.1) + '@chakra-ui/anatomy': 2.3.5 + '@chakra-ui/styled-system': 2.12.1(react@18.3.1) + '@chakra-ui/utils': 2.2.3(react@18.3.1) color2k: 2.0.3 transitivePeerDependencies: - react @@ -948,15 +948,15 @@ packages: '@chakra-ui/theme-tools': 2.1.2(@chakra-ui/styled-system@2.9.2) dev: false - /@chakra-ui/theme@3.4.6(@chakra-ui/styled-system@2.11.2)(react@18.3.1): - resolution: {integrity: sha512-ZwFBLfiMC3URwaO31ONXoKH9k0TX0OW3UjdPF3EQkQpYyrk/fm36GkkzajjtdpWEd7rzDLRsQjPmvwNaSoNDtg==} + /@chakra-ui/theme@3.4.7(@chakra-ui/styled-system@2.12.1)(react@18.3.1): + resolution: {integrity: sha512-pfewthgZTFNUYeUwGvhPQO/FTIyf375cFV1AT8N1y0aJiw4KDe7YTGm7p0aFy4AwAjH2ydMgeEx/lua4tx8qyQ==} peerDependencies: '@chakra-ui/styled-system': '>=2.8.0' dependencies: - '@chakra-ui/anatomy': 2.3.4 - '@chakra-ui/styled-system': 2.11.2(react@18.3.1) - '@chakra-ui/theme-tools': 2.2.6(@chakra-ui/styled-system@2.11.2)(react@18.3.1) - '@chakra-ui/utils': 2.2.2(react@18.3.1) + '@chakra-ui/anatomy': 2.3.5 + '@chakra-ui/styled-system': 2.12.1(react@18.3.1) + '@chakra-ui/theme-tools': 2.2.7(@chakra-ui/styled-system@2.12.1)(react@18.3.1) + '@chakra-ui/utils': 2.2.3(react@18.3.1) transitivePeerDependencies: - react dev: false @@ -981,8 +981,8 @@ packages: lodash.mergewith: 4.6.2 dev: false - /@chakra-ui/utils@2.2.2(react@18.3.1): - resolution: {integrity: sha512-jUPLT0JzRMWxpdzH6c+t0YMJYrvc5CLericgITV3zDSXblkfx3DsYXqU11DJTSGZI9dUKzM1Wd0Wswn4eJwvFQ==} + /@chakra-ui/utils@2.2.3(react@18.3.1): + resolution: {integrity: sha512-cldoCQuexZ6e07/9hWHKD4l1QXXlM1Nax9tuQOBvVf/EgwNZt3nZu8zZRDFlhAOKCTQDkmpLTTu+eXXjChNQOw==} peerDependencies: react: '>=16.8.0' dependencies: @@ -1675,20 +1675,20 @@ packages: prettier: 3.3.3 dev: true - /@invoke-ai/ui-library@0.0.43(@chakra-ui/form-control@2.2.0)(@chakra-ui/icon@3.2.0)(@chakra-ui/media-query@3.3.0)(@chakra-ui/menu@2.2.1)(@chakra-ui/spinner@2.1.0)(@chakra-ui/system@2.6.2)(@fontsource-variable/inter@5.1.0)(@types/react@18.3.11)(i18next@23.15.1)(react-dom@18.3.1)(react@18.3.1): - resolution: {integrity: sha512-t3fPYyks07ue3dEBPJuTHbeDLnDckDCOrtvc07mMDbLOnlPEZ0StaeiNGH+oO8qLzAuMAlSTdswgHfzTc2MmPw==} + /@invoke-ai/ui-library@0.0.44(@chakra-ui/form-control@2.2.0)(@chakra-ui/icon@3.2.0)(@chakra-ui/media-query@3.3.0)(@chakra-ui/menu@2.2.1)(@chakra-ui/spinner@2.1.0)(@chakra-ui/system@2.6.2)(@fontsource-variable/inter@5.1.0)(@types/react@18.3.11)(i18next@23.15.1)(react-dom@18.3.1)(react@18.3.1): + resolution: {integrity: sha512-PDseHmdr8oi8cmrpx3UwIYHn4NduAJX2R0pM0pyM54xrCMPMgYiCbC/eOs8Gt4fBc2ziiPZ9UGoW4evnE3YJsg==} peerDependencies: '@fontsource-variable/inter': ^5.0.16 react: ^18.2.0 react-dom: ^18.2.0 dependencies: - '@chakra-ui/anatomy': 2.3.4 - '@chakra-ui/icons': 2.2.4(@chakra-ui/react@2.10.2)(react@18.3.1) + '@chakra-ui/anatomy': 2.2.2 + '@chakra-ui/icons': 2.2.4(@chakra-ui/react@2.10.4)(react@18.3.1) '@chakra-ui/layout': 2.3.1(@chakra-ui/system@2.6.2)(react@18.3.1) '@chakra-ui/portal': 2.1.0(react-dom@18.3.1)(react@18.3.1) - '@chakra-ui/react': 2.10.2(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(@types/react@18.3.11)(framer-motion@11.10.0)(react-dom@18.3.1)(react@18.3.1) - '@chakra-ui/styled-system': 2.11.2(react@18.3.1) - '@chakra-ui/theme-tools': 2.2.6(@chakra-ui/styled-system@2.11.2)(react@18.3.1) + '@chakra-ui/react': 2.10.4(@emotion/react@11.13.3)(@emotion/styled@11.13.0)(@types/react@18.3.11)(framer-motion@11.10.0)(react-dom@18.3.1)(react@18.3.1) + '@chakra-ui/styled-system': 2.9.2 + '@chakra-ui/theme-tools': 2.1.2(@chakra-ui/styled-system@2.9.2) '@emotion/react': 11.13.3(@types/react@18.3.11)(react@18.3.1) '@emotion/styled': 11.13.0(@emotion/react@11.13.3)(@types/react@18.3.11)(react@18.3.1) '@fontsource-variable/inter': 5.1.0 diff --git a/invokeai/frontend/web/public/locales/en.json b/invokeai/frontend/web/public/locales/en.json index 8b679897138..9ea13a37382 100644 --- a/invokeai/frontend/web/public/locales/en.json +++ b/invokeai/frontend/web/public/locales/en.json @@ -2124,5 +2124,67 @@ "readReleaseNotes": "Read Release Notes", "watchRecentReleaseVideos": "Watch Recent Release Videos", "watchUiUpdatesOverview": "Watch UI Updates Overview" + }, + "supportVideos": { + "supportVideos": "Support Videos", + "gettingStarted": "Getting Started", + "controlCanvas": "Control Canvas", + "watch": "Watch", + "studioSessionsDesc1": "Check out the for Invoke deep dives.", + "studioSessionsDesc2": "Join our to participate in the live sessions and ask questions. Sessions are uploaded to the playlist the following week.", + "videos": { + "creatingYourFirstImage": { + "title": "Creating Your First Image", + "description": "Introduction to creating an image from scratch using Invoke's tools." + }, + "usingControlLayersAndReferenceGuides": { + "title": "Using Control Layers and Reference Guides", + "description": "Learn how to guide your image creation with control layers and reference images." + }, + "understandingImageToImageAndDenoising": { + "title": "Understanding Image-to-Image and Denoising", + "description": "Overview of image-to-image transformations and denoising in Invoke." + }, + "exploringAIModelsAndConceptAdapters": { + "title": "Exploring AI Models and Concept Adapters", + "description": "Dive into AI models and how to use concept adapters for creative control." + }, + "creatingAndComposingOnInvokesControlCanvas": { + "title": "Creating and Composing on Invoke's Control Canvas", + "description": "Learn to compose images using Invoke's control canvas." + }, + "upscaling": { + "title": "Upscaling", + "description": "How to upscale images with Invoke's tools to enhance resolution." + }, + "howDoIGenerateAndSaveToTheGallery": { + "title": "How Do I Generate and Save to the Gallery?", + "description": "Steps to generate and save images to the gallery." + }, + "howDoIEditOnTheCanvas": { + "title": "How Do I Edit on the Canvas?", + "description": "Guide to editing images directly on the canvas." + }, + "howDoIDoImageToImageTransformation": { + "title": "How Do I Do Image-to-Image Transformation?", + "description": "Tutorial on performing image-to-image transformations in Invoke." + }, + "howDoIUseControlNetsAndControlLayers": { + "title": "How Do I Use Control Nets and Control Layers?", + "description": "Learn to apply control layers and controlnets to your images." + }, + "howDoIUseGlobalIPAdaptersAndReferenceImages": { + "title": "How Do I Use Global IP Adapters and Reference Images?", + "description": "Introduction to adding reference images and global IP adapters." + }, + "howDoIUseInpaintMasks": { + "title": "How Do I Use Inpaint Masks?", + "description": "How to apply inpaint masks for image correction and variation." + }, + "howDoIOutpaint": { + "title": "How Do I Outpaint?", + "description": "Guide to outpainting beyond the original image borders." + } + } } } diff --git a/invokeai/frontend/web/src/app/components/App.tsx b/invokeai/frontend/web/src/app/components/App.tsx index 67003f5b62e..2902c344adb 100644 --- a/invokeai/frontend/web/src/app/components/App.tsx +++ b/invokeai/frontend/web/src/app/components/App.tsx @@ -27,6 +27,7 @@ import { ClearQueueConfirmationsAlertDialog } from 'features/queue/components/Cl import { DeleteStylePresetDialog } from 'features/stylePresets/components/DeleteStylePresetDialog'; import { StylePresetModal } from 'features/stylePresets/components/StylePresetForm/StylePresetModal'; import RefreshAfterResetModal from 'features/system/components/SettingsModal/RefreshAfterResetModal'; +import { VideosModal } from 'features/system/components/VideosModal/VideosModal'; import { configChanged } from 'features/system/store/configSlice'; import { selectLanguage } from 'features/system/store/systemSelectors'; import { AppContent } from 'features/ui/components/AppContent'; @@ -108,6 +109,7 @@ const App = ({ config = DEFAULT_CONFIG, studioInitAction }: Props) => { + ); }; diff --git a/invokeai/frontend/web/src/features/system/components/VideosModal/VideoCard.tsx b/invokeai/frontend/web/src/features/system/components/VideosModal/VideoCard.tsx new file mode 100644 index 00000000000..8e4275ea8b7 --- /dev/null +++ b/invokeai/frontend/web/src/features/system/components/VideosModal/VideoCard.tsx @@ -0,0 +1,30 @@ +import { ExternalLink, Flex, Spacer, Text } from '@invoke-ai/ui-library'; +import type { VideoData } from 'features/system/components/VideosModal/data'; +import { memo } from 'react'; +import { useTranslation } from 'react-i18next'; + +const formatTime = ({ minutes, seconds }: { minutes: number; seconds: number }) => { + return `${minutes}:${seconds.toString().padStart(2, '0')}`; +}; + +export const VideoCard = memo(({ video }: { video: VideoData }) => { + const { t } = useTranslation(); + const { tKey, link, length } = video; + return ( + + + + {t(`supportVideos.videos.${tKey}.title`)} + + + {formatTime(length)} + + + + {t(`supportVideos.videos.${tKey}.description`)} + + + ); +}); + +VideoCard.displayName = 'VideoCard'; diff --git a/invokeai/frontend/web/src/features/system/components/VideosModal/VideoCardList.tsx b/invokeai/frontend/web/src/features/system/components/VideosModal/VideoCardList.tsx new file mode 100644 index 00000000000..4140b05c03d --- /dev/null +++ b/invokeai/frontend/web/src/features/system/components/VideosModal/VideoCardList.tsx @@ -0,0 +1,20 @@ +import { Divider } from '@invoke-ai/ui-library'; +import { StickyScrollable } from 'features/system/components/StickyScrollable'; +import { gettingStartedVideos, type VideoData } from 'features/system/components/VideosModal/data'; +import { VideoCard } from 'features/system/components/VideosModal/VideoCard'; +import { Fragment, memo } from 'react'; + +export const VideoCardList = memo(({ category, videos }: { category: string; videos: VideoData[] }) => { + return ( + + {videos.map((video, i) => ( + + + {i < gettingStartedVideos.length - 1 && } + + ))} + + ); +}); + +VideoCardList.displayName = 'VideoCardList'; diff --git a/invokeai/frontend/web/src/features/system/components/VideosModal/VideosModal.tsx b/invokeai/frontend/web/src/features/system/components/VideosModal/VideosModal.tsx new file mode 100644 index 00000000000..5f7c9e025cb --- /dev/null +++ b/invokeai/frontend/web/src/features/system/components/VideosModal/VideosModal.tsx @@ -0,0 +1,79 @@ +import { + ExternalLink, + Flex, + Modal, + ModalBody, + ModalCloseButton, + ModalContent, + ModalFooter, + ModalHeader, + ModalOverlay, + Text, +} from '@invoke-ai/ui-library'; +import ScrollableContent from 'common/components/OverlayScrollbars/ScrollableContent'; +import { buildUseDisclosure } from 'common/hooks/useBoolean'; +import { + controlCanvasVideos, + gettingStartedVideos, + studioSessionsPlaylistLink, +} from 'features/system/components/VideosModal/data'; +import { VideoCardList } from 'features/system/components/VideosModal/VideoCardList'; +import { discordLink } from 'features/system/store/constants'; +import { memo } from 'react'; +import { Trans, useTranslation } from 'react-i18next'; + +export const [useVideosModal] = buildUseDisclosure(false); + +const StudioSessionsPlaylistLink = () => { + return ( + + ); +}; + +const DiscordLink = () => { + return ; +}; + +const components = { + StudioSessionsPlaylistLink: , + DiscordLink: , +}; + +export const VideosModal = memo(() => { + const { t } = useTranslation(); + const videosModal = useVideosModal(); + + return ( + + + + {t('supportVideos.supportVideos')} + + + + + + + + + + + + + + + + + + + + + ); +}); + +VideosModal.displayName = 'VideosModal'; diff --git a/invokeai/frontend/web/src/features/system/components/VideosModal/VideosModalButton.tsx b/invokeai/frontend/web/src/features/system/components/VideosModal/VideosModalButton.tsx new file mode 100644 index 00000000000..7436b8ec7f7 --- /dev/null +++ b/invokeai/frontend/web/src/features/system/components/VideosModal/VideosModalButton.tsx @@ -0,0 +1,20 @@ +import { IconButton } from '@invoke-ai/ui-library'; +import { useVideosModal } from 'features/system/components/VideosModal/VideosModal'; +import { memo } from 'react'; +import { useTranslation } from 'react-i18next'; +import { PiYoutubeLogoFill } from 'react-icons/pi'; + +export const VideosModalButton = memo(() => { + const { t } = useTranslation(); + const videosModal = useVideosModal(); + return ( + } + boxSize={8} + onClick={videosModal.open} + /> + ); +}); +VideosModalButton.displayName = 'VideosModalButton'; diff --git a/invokeai/frontend/web/src/features/system/components/VideosModal/data.ts b/invokeai/frontend/web/src/features/system/components/VideosModal/data.ts new file mode 100644 index 00000000000..0531c0d2e34 --- /dev/null +++ b/invokeai/frontend/web/src/features/system/components/VideosModal/data.ts @@ -0,0 +1,88 @@ +/** + * To add a support video, you'll need to add the video to the list below. + * + * The `tKey` is a sub-key in the translation file `invokeai/frontend/web/public/locales/en.json`. + * Add the title and description under `supportVideos.videos`, following the existing format. + */ + +export type VideoData = { + tKey: string; + link: string; + length: { + minutes: number; + seconds: number; + }; +}; + +export const gettingStartedVideos: VideoData[] = [ + { + tKey: 'creatingYourFirstImage', + link: 'https://www.youtube.com/watch?v=jVi2XgSGrfY&list=PLvWK1Kc8iXGrQy8r9TYg6QdUuJ5MMx-ZO&index=1&t=29s&pp=iAQB', + length: { minutes: 6, seconds: 0 }, + }, + { + tKey: 'usingControlLayersAndReferenceGuides', + link: 'https://www.youtube.com/watch?v=crgw6bEgyrw&list=PLvWK1Kc8iXGrQy8r9TYg6QdUuJ5MMx-ZO&index=2&t=70s&pp=iAQB', + length: { minutes: 5, seconds: 30 }, + }, + { + tKey: 'understandingImageToImageAndDenoising', + link: 'https://www.youtube.com/watch?v=tvj8-0s6S2U&list=PLvWK1Kc8iXGrQy8r9TYg6QdUuJ5MMx-ZO&index=3&t=1s&pp=iAQB', + length: { minutes: 2, seconds: 37 }, + }, + { + tKey: 'exploringAIModelsAndConceptAdapters', + link: 'https://www.youtube.com/watch?v=iwBmBQMZ0UA&list=PLvWK1Kc8iXGrQy8r9TYg6QdUuJ5MMx-ZO&index=4&pp=iAQB', + length: { minutes: 8, seconds: 52 }, + }, + { + tKey: 'creatingAndComposingOnInvokesControlCanvas', + link: 'https://www.youtube.com/watch?v=MohWv5GZVGM&list=PLvWK1Kc8iXGrQy8r9TYg6QdUuJ5MMx-ZO&index=5&t=28s&pp=iAQB', + length: { minutes: 13, seconds: 56 }, + }, + { + tKey: 'upscaling', + link: 'https://www.youtube.com/watch?v=OCb19_P0nro&list=PLvWK1Kc8iXGrQy8r9TYg6QdUuJ5MMx-ZO&index=6&t=2s&pp=iAQB', + length: { minutes: 4, seconds: 0 }, + }, +]; + +export const controlCanvasVideos: VideoData[] = [ + { + tKey: 'howDoIGenerateAndSaveToTheGallery', + link: 'https://youtu.be/Tl-69JvwJ2s?si=dbjmBc1iDAUpE1k5&t=26', + length: { minutes: 0, seconds: 49 }, + }, + { + tKey: 'howDoIEditOnTheCanvas', + link: 'https://youtu.be/Tl-69JvwJ2s?si=U_bFl9HsvSuejbxp&t=76', + length: { minutes: 0, seconds: 58 }, + }, + { + tKey: 'howDoIDoImageToImageTransformation', + link: 'https://youtu.be/Tl-69JvwJ2s?si=fjhTeY-yZ3qsEzEM&t=138', + length: { minutes: 0, seconds: 51 }, + }, + { + tKey: 'howDoIUseControlNetsAndControlLayers', + link: 'https://youtu.be/Tl-69JvwJ2s?si=x5KcYvkHbvR9ifsX&t=192', + length: { minutes: 1, seconds: 41 }, + }, + { + tKey: 'howDoIUseGlobalIPAdaptersAndReferenceImages', + link: 'https://youtu.be/Tl-69JvwJ2s?si=O940rNHiHGKXknK2&t=297', + length: { minutes: 0, seconds: 43 }, + }, + { + tKey: 'howDoIUseInpaintMasks', + link: 'https://youtu.be/Tl-69JvwJ2s?si=3DZhmerkzUmvJJSn&t=345', + length: { minutes: 1, seconds: 9 }, + }, + { + tKey: 'howDoIOutpaint', + link: 'https://youtu.be/Tl-69JvwJ2s?si=IIwkGZLq1PfLf80Q&t=420', + length: { minutes: 0, seconds: 48 }, + }, +]; + +export const studioSessionsPlaylistLink = 'https://www.youtube.com/playlist?list=PLvWK1Kc8iXGq_8tWZqnwDVaf9uhlDC09U'; diff --git a/invokeai/frontend/web/src/features/ui/components/VerticalNavBar.tsx b/invokeai/frontend/web/src/features/ui/components/VerticalNavBar.tsx index 2dbb87e8b50..a5925d8de58 100644 --- a/invokeai/frontend/web/src/features/ui/components/VerticalNavBar.tsx +++ b/invokeai/frontend/web/src/features/ui/components/VerticalNavBar.tsx @@ -4,6 +4,7 @@ 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 { VideosModalButton } from 'features/system/components/VideosModal/VideosModalButton'; import { TabMountGate } from 'features/ui/components/TabMountGate'; import { memo } from 'react'; import { useTranslation } from 'react-i18next'; @@ -39,6 +40,7 @@ export const VerticalNavBar = memo(() => { + {customNavComponent ? customNavComponent : } );