diff --git a/packages/react/src/components/video/VideoCard.tsx b/packages/react/src/components/video/VideoCard.tsx index 325766eb5..6348056d6 100644 --- a/packages/react/src/components/video/VideoCard.tsx +++ b/packages/react/src/components/video/VideoCard.tsx @@ -11,7 +11,10 @@ import { clsx } from "clsx"; import { VideoThumbnail } from "./VideoThumbnail"; import { usePreferredName } from "@/store/settings"; import { useAtomValue } from "jotai"; -import { selectedVideoSetReadonlyAtom } from "@/hooks/useVideoSelection"; +import { + selectedVideoSetReadonlyAtom, + useVideoSelection, +} from "@/hooks/useVideoSelection"; export type VideoCardType = VideoRef & Partial & @@ -64,59 +67,86 @@ export function VideoCard({ !isTwitch && video.status === "live" && video.link ? video.link : `/watch/${video.id}`; + const videoIsPlaceholder = video.type === "placeholder"; + const thumbnailSrc = videoIsPlaceholder + ? video.thumbnail + : makeYtThumbnailUrl(video.id, size); - const thumbnailSrc = - video.type === "placeholder" - ? video.thumbnail - : makeYtThumbnailUrl(video.id, size); - - const externalLink = - video.type === "placeholder" ? video.link : `https://youtu.be/${video.id}`; + const externalLink = videoIsPlaceholder + ? video.link + : `https://youtu.be/${video.id}`; const videoTarget = !isTwitch && video.placeholderType === "external-stream" ? "_blank" : undefined; - const [open, setOpen] = useState(false); // placeholder popup state. + const [placeholderOpen, setPlaceholderOpen] = useState(false); // placeholder popup state. const selectedSet = useAtomValue(selectedVideoSetReadonlyAtom); + const { selectionMode, addVideo, removeVideo } = useVideoSelection(); const goToVideoClickHandler = useCallback( (evt: React.MouseEvent) => { - if ((evt.target as HTMLElement).closest("a")) { - if (video.type === "placeholder") { - console.info("took over click.", evt); - setOpen(true); + console.info("JS Video Click Handling", evt); + const isLinkClick = (evt.target as HTMLElement).closest("a"); + if (isLinkClick) { + // Handle selection mode + if (selectionMode) { + if (selectedSet.includes(video.id)) { + removeVideo(video.id); + } else { + addVideo(video as PlaceholderVideo); + } evt.preventDefault(); evt.stopPropagation(); return; } + + if (videoIsPlaceholder) { + evt.preventDefault(); + evt.stopPropagation(); + return setPlaceholderOpen(true); + } + console.info("no action b/c closest element is a link.", evt); return; } - console.info("JS video click handling", evt); - // clicked a non-link part of the video card. - if (evt.ctrlKey) { + // clicked a non-link part of the video card. Prevent default behavior. + evt.preventDefault(); + evt.stopPropagation(); + + if (evt.ctrlKey || evt.metaKey) { /** Control clicking a non-link part always goes to the external link no matter what the context */ - window.open(videoHref, "_blank"); - evt.preventDefault(); - evt.stopPropagation(); - } else { - if (video.type === "placeholder") { - setOpen(true); - evt.preventDefault(); - evt.stopPropagation(); - return; + return window.open(videoHref, "_blank"); + } + + // Handle selection mode + if (selectionMode) { + if (selectedSet.includes(video.id)) { + removeVideo(video.id); + } else { + addVideo(video as PlaceholderVideo); } - navigate(videoHref, { - state: { video }, - }); - evt.preventDefault(); - evt.stopPropagation(); + return; } + + if (videoIsPlaceholder) { + return setPlaceholderOpen(true); + } + + navigate(videoHref, { state: { video } }); }, - [videoHref, navigate, video], + [ + selectionMode, + videoIsPlaceholder, + navigate, + videoHref, + video, + selectedSet, + removeVideo, + addVideo, + ], ); /** @@ -141,8 +171,10 @@ export function VideoCard({ (size == "list" || size == "sm") && "group relative flex gap-4 py-2", (size == "md" || size == "lg") && "group flex w-full flex-col gap-4", onClick && "cursor-pointer", - selectedSet?.includes(video.id) && - "ring-offset-base-2 ring-offset-2 ring-2 ring-primary rounded-lg", + selectionMode && + (selectedSet?.includes(video.id) + ? "ring-offset-base-2 ring-offset-2 ring-4 ring-primary-8 rounded-lg " + : "ring-offset-base-2 ring-offset-2 ring-4 ring-base-6 rounded-lg saturate-[0.75] brightness-75 opacity-50"), ]), thumbnailLink: clsx([ size == "list" && @@ -165,24 +197,7 @@ export function VideoCard({ "line-clamp-1 text-xs text-base-11 hover:text-base-12 @lg:text-sm", scheduleText: "text-sm @lg:text-sm text-base-11", }), - [onClick, size, selectedSet, video.id], - ); - - // TODO: possibly externalize the video menu using some sort of 'atom' to prevent it from being re-rendered on every card. - const videoMenu = ( - - - + [size, onClick, selectionMode, selectedSet, video.id], ); const chName = usePreferredName(video.channel); @@ -207,7 +222,7 @@ export function VideoCard({ )} - {videoMenu} -
- - - -
+ + + + {videoIsPlaceholder && ( +
+ + + +
+ )} );