From 9ff6ada15bde3cb9b4bb2974eb8d62f73ef17ed9 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 7 Jul 2025 16:28:34 +0000 Subject: [PATCH] Add support for video playlists in support videos modal Co-authored-by: kent --- invokeai/frontend/web/public/locales/en.json | 15 +++++++++ .../ModelInstallQueue/ModelInstallQueue.tsx | 4 +-- .../components/VideosModal/PlaylistCard.tsx | 33 +++++++++++++++++++ .../VideosModal/PlaylistCardList.tsx | 20 +++++++++++ .../components/VideosModal/VideoCardList.tsx | 4 +-- .../components/VideosModal/VideosModal.tsx | 5 +-- .../system/components/VideosModal/data.ts | 24 ++++++++++++-- 7 files changed, 96 insertions(+), 9 deletions(-) create mode 100644 invokeai/frontend/web/src/features/system/components/VideosModal/PlaylistCard.tsx create mode 100644 invokeai/frontend/web/src/features/system/components/VideosModal/PlaylistCardList.tsx diff --git a/invokeai/frontend/web/public/locales/en.json b/invokeai/frontend/web/public/locales/en.json index 04a96e5467..84eef9d6b2 100644 --- a/invokeai/frontend/web/public/locales/en.json +++ b/invokeai/frontend/web/public/locales/en.json @@ -2564,11 +2564,26 @@ }, "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 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.", + "playlists": { + "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." + }, + "studioSessions": { + "title": "Studio Sessions", + "description": "Deep dive sessions exploring advanced Invoke features, creative workflows, and community discussions." + } + }, "videos": { "creatingYourFirstImage": { "title": "Creating Your First Image", diff --git a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/AddModelPanel/ModelInstallQueue/ModelInstallQueue.tsx b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/AddModelPanel/ModelInstallQueue/ModelInstallQueue.tsx index f124462e8b..c2443dde6b 100644 --- a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/AddModelPanel/ModelInstallQueue/ModelInstallQueue.tsx +++ b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/AddModelPanel/ModelInstallQueue/ModelInstallQueue.tsx @@ -55,9 +55,7 @@ export const ModelInstallQueue = memo(() => { - {data?.map((model) => ( - - ))} + {data?.map((model) => )} diff --git a/invokeai/frontend/web/src/features/system/components/VideosModal/PlaylistCard.tsx b/invokeai/frontend/web/src/features/system/components/VideosModal/PlaylistCard.tsx new file mode 100644 index 0000000000..4c361fb3bd --- /dev/null +++ b/invokeai/frontend/web/src/features/system/components/VideosModal/PlaylistCard.tsx @@ -0,0 +1,33 @@ +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 ( + + + + {t(`supportVideos.playlists.${tKey}.title`)} + + + {t('supportVideos.videoCount', { count: videoCount })} + + + + {t(`supportVideos.playlists.${tKey}.description`)} + + + ); +}); + +PlaylistCard.displayName = 'PlaylistCard'; diff --git a/invokeai/frontend/web/src/features/system/components/VideosModal/PlaylistCardList.tsx b/invokeai/frontend/web/src/features/system/components/VideosModal/PlaylistCardList.tsx new file mode 100644 index 0000000000..d29f4bb206 --- /dev/null +++ b/invokeai/frontend/web/src/features/system/components/VideosModal/PlaylistCardList.tsx @@ -0,0 +1,20 @@ +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 ( + + {playlists.map((playlist, i) => ( + + + {i < playlists.length - 1 && } + + ))} + + ); +}); + +PlaylistCardList.displayName = 'PlaylistCardList'; diff --git a/invokeai/frontend/web/src/features/system/components/VideosModal/VideoCardList.tsx b/invokeai/frontend/web/src/features/system/components/VideosModal/VideoCardList.tsx index 4140b05c03..188a2814a3 100644 --- a/invokeai/frontend/web/src/features/system/components/VideosModal/VideoCardList.tsx +++ b/invokeai/frontend/web/src/features/system/components/VideosModal/VideoCardList.tsx @@ -1,6 +1,6 @@ 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 type { VideoData } from 'features/system/components/VideosModal/data'; import { VideoCard } from 'features/system/components/VideosModal/VideoCard'; import { Fragment, memo } from 'react'; @@ -10,7 +10,7 @@ export const VideoCardList = memo(({ category, videos }: { category: string; vid {videos.map((video, i) => ( - {i < gettingStartedVideos.length - 1 && } + {i < videos.length - 1 && } ))} diff --git a/invokeai/frontend/web/src/features/system/components/VideosModal/VideosModal.tsx b/invokeai/frontend/web/src/features/system/components/VideosModal/VideosModal.tsx index 05419be5f1..8ba63e2a33 100644 --- a/invokeai/frontend/web/src/features/system/components/VideosModal/VideosModal.tsx +++ b/invokeai/frontend/web/src/features/system/components/VideosModal/VideosModal.tsx @@ -15,9 +15,10 @@ import ScrollableContent from 'common/components/OverlayScrollbars/ScrollableCon import { buildUseDisclosure } from 'common/hooks/useBoolean'; import { controlCanvasVideos, - gettingStartedVideos, studioSessionsPlaylistLink, + supportPlaylists, } from 'features/system/components/VideosModal/data'; +import { PlaylistCardList } from 'features/system/components/VideosModal/PlaylistCardList'; import { VideoCardList } from 'features/system/components/VideosModal/VideoCardList'; import { videoModalLinkClicked } from 'features/system/store/actions'; import { discordLink } from 'features/system/store/constants'; @@ -86,7 +87,7 @@ export const VideosModal = memo(() => { - + diff --git a/invokeai/frontend/web/src/features/system/components/VideosModal/data.ts b/invokeai/frontend/web/src/features/system/components/VideosModal/data.ts index 0b194ee5a6..3e7864779e 100644 --- a/invokeai/frontend/web/src/features/system/components/VideosModal/data.ts +++ b/invokeai/frontend/web/src/features/system/components/VideosModal/data.ts @@ -1,10 +1,30 @@ /** - * To add a support video, you'll need to add the video to the list below. + * To add a support video playlist, you'll need to add the playlist 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. + * Add the title and description under `supportVideos.playlists`, 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;