Refactor support videos modal to simplify video and playlist handling

Co-authored-by: kent <kent@invoke.ai>
This commit is contained in:
Cursor Agent
2025-07-07 16:36:53 +00:00
committed by psychedelicious
parent ac26aa9508
commit c9d4e2b761
5 changed files with 12 additions and 209 deletions

View File

@@ -2564,17 +2564,11 @@
},
"supportVideos": {
"supportVideos": "Support Videos",
"supportPlaylists": "Video Playlists",
"gettingStarted": "Getting Started",
"controlCanvas": "Control Canvas",
"watch": "Watch",
"watchPlaylist": "Watch Playlist",
"videoCount": "{{count}} videos",
"videoCount_one": "{{count}} video",
"videoCount_other": "{{count}} videos",
"studioSessionsDesc1": "Check out the <StudioSessionsPlaylistLink /> for Invoke deep dives.",
"studioSessionsDesc2": "Join our <DiscordLink /> to participate in the live sessions and ask questions. Sessions are uploaded to the playlist the following week.",
"playlists": {
"videos": {
"gettingStarted": {
"title": "Getting Started with Invoke",
"description": "Complete video series covering everything you need to know to get started with Invoke, from creating your first image to advanced techniques."
@@ -2583,60 +2577,6 @@
"title": "Studio Sessions",
"description": "Deep dive sessions exploring advanced Invoke features, creative workflows, and community discussions."
}
},
"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."
}
}
}
}

View File

@@ -1,33 +0,0 @@
import { ExternalLink, Flex, Spacer, Text } from '@invoke-ai/ui-library';
import { useAppDispatch } from 'app/store/storeHooks';
import type { PlaylistData } from 'features/system/components/VideosModal/data';
import { videoModalLinkClicked } from 'features/system/store/actions';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
export const PlaylistCard = memo(({ playlist }: { playlist: PlaylistData }) => {
const { t } = useTranslation();
const dispatch = useAppDispatch();
const { tKey, link, videoCount } = playlist;
const handleLinkClick = useCallback(() => {
dispatch(videoModalLinkClicked(t(`supportVideos.playlists.${tKey}.title`)));
}, [dispatch, t, tKey]);
return (
<Flex flexDir="column" gap={1}>
<Flex alignItems="center" gap={2}>
<Text fontSize="md" fontWeight="semibold">
{t(`supportVideos.playlists.${tKey}.title`)}
</Text>
<Spacer />
<Text variant="subtext">{t('supportVideos.videoCount', { count: videoCount })}</Text>
<ExternalLink fontSize="sm" href={link} label={t('supportVideos.watchPlaylist')} onClick={handleLinkClick} />
</Flex>
<Text fontSize="md" variant="subtext">
{t(`supportVideos.playlists.${tKey}.description`)}
</Text>
</Flex>
);
});
PlaylistCard.displayName = 'PlaylistCard';

View File

@@ -1,20 +0,0 @@
import { Divider } from '@invoke-ai/ui-library';
import { StickyScrollable } from 'features/system/components/StickyScrollable';
import type { PlaylistData } from 'features/system/components/VideosModal/data';
import { PlaylistCard } from 'features/system/components/VideosModal/PlaylistCard';
import { Fragment, memo } from 'react';
export const PlaylistCardList = memo(({ category, playlists }: { category: string; playlists: PlaylistData[] }) => {
return (
<StickyScrollable title={category}>
{playlists.map((playlist, i) => (
<Fragment key={`${playlist.tKey}-${i}`}>
<PlaylistCard playlist={playlist} />
{i < playlists.length - 1 && <Divider />}
</Fragment>
))}
</StickyScrollable>
);
});
PlaylistCardList.displayName = 'PlaylistCardList';

View File

@@ -13,12 +13,7 @@ import {
import { useAppDispatch } from 'app/store/storeHooks';
import ScrollableContent from 'common/components/OverlayScrollbars/ScrollableContent';
import { buildUseDisclosure } from 'common/hooks/useBoolean';
import {
controlCanvasVideos,
studioSessionsPlaylistLink,
supportPlaylists,
} from 'features/system/components/VideosModal/data';
import { PlaylistCardList } from 'features/system/components/VideosModal/PlaylistCardList';
import { studioSessionsPlaylistLink, supportVideos } from 'features/system/components/VideosModal/data';
import { VideoCardList } from 'features/system/components/VideosModal/VideoCardList';
import { videoModalLinkClicked } from 'features/system/store/actions';
import { discordLink } from 'features/system/store/constants';
@@ -87,8 +82,7 @@ export const VideosModal = memo(() => {
<Trans i18nKey="supportVideos.studioSessionsDesc2" components={components} />
</Text>
</Flex>
<PlaylistCardList category={t('supportVideos.supportPlaylists')} playlists={supportPlaylists} />
<VideoCardList category={t('supportVideos.controlCanvas')} videos={controlCanvasVideos} />
<VideoCardList category={t('supportVideos.supportVideos')} videos={supportVideos} />
</Flex>
</ScrollableContent>
</ModalBody>

View File

@@ -1,30 +1,10 @@
/**
* To add a support video playlist, you'll need to add the playlist to the list below.
* 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.playlists`, following the existing format.
* Add the title and description under `supportVideos.videos`, following the existing format.
*/
export type PlaylistData = {
tKey: string;
link: string;
videoCount: number;
};
export const supportPlaylists: PlaylistData[] = [
{
tKey: 'gettingStarted',
link: 'https://www.youtube.com/watch?v=jVi2XgSGrfY&list=PLvWK1Kc8iXGrQy8r9TYg6QdUuJ5MMx-ZO&pp=gAQB0gcJCV8EOCosWNin',
videoCount: 6,
},
{
tKey: 'studioSessions',
link: 'https://www.youtube.com/watch?v=91ZgeeqL7Bo&list=PLvWK1Kc8iXGq_8tWZqnwDVaf9uhlDC09U&pp=gAQB',
videoCount: 10,
},
];
// Legacy video data - keeping for backward compatibility if needed
export type VideoData = {
tKey: string;
link: string;
@@ -34,74 +14,16 @@ export type VideoData = {
};
};
export const gettingStartedVideos: VideoData[] = [
export const supportVideos: 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: 'gettingStarted',
link: 'https://www.youtube.com/watch?v=jVi2XgSGrfY&list=PLvWK1Kc8iXGrQy8r9TYg6QdUuJ5MMx-ZO&pp=gAQB0gcJCV8EOCosWNin',
length: { minutes: 0, seconds: 0 }, // Playlist doesn't have a single duration
},
{
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=O4LaFcYFxlA',
length: { minutes: 2, seconds: 52 },
},
{
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 },
tKey: 'studioSessions',
link: 'https://www.youtube.com/watch?v=91ZgeeqL7Bo&list=PLvWK1Kc8iXGq_8tWZqnwDVaf9uhlDC09U&pp=gAQB',
length: { minutes: 0, seconds: 0 }, // Playlist doesn't have a single duration
},
];