"use client" import { useEffect, useRef, useState, useTransition } from "react" import { RiCheckboxCircleFill } from "react-icons/ri" import { PiShareFatLight } from "react-icons/pi" import { BsHeadsetVr } from "react-icons/bs" import CopyToClipboard from "react-copy-to-clipboard" import { LuCopyCheck } from "react-icons/lu" import { LuScrollText } from "react-icons/lu" import { BiCameraMovie } from "react-icons/bi" import { useLocalStorage } from "usehooks-ts" import { useStore } from "@/app/state/useStore" import { cn } from "@/lib/utils/cn" import { ActionButton, actionButtonClassName } from "@/components/interface/action-button" import { RecommendedVideos } from "@/components/interface/recommended-videos" import { isCertifiedUser } from "@/app/certification" import { countNewMediaView } from "@/app/api/actions/stats" import { formatTimeAgo } from "@/lib/formatters/formatTimeAgo" import { DefaultAvatar } from "@/components/interface/default-avatar" import { LikeButton } from "@/components/interface/like-button" import { ReportModal } from "../report-modal" import { formatLargeNumber } from "@/lib/formatters/formatLargeNumber" import { CommentList } from "@/components/interface/comment-list" import { Input } from "@/components/ui/input" import { localStorageKeys } from "@/app/state/localStorageKeys" import { defaultSettings } from "@/app/state/defaultSettings" import { getComments, submitComment } from "@/app/api/actions/comments" import { useCurrentUser } from "@/app/state/useCurrentUser" import { parseMediaProjectionType } from "@/lib/utils/parseMediaProjectionType" import { MediaPlayer } from "@/components/interface/media-player" export function PublicMediaView() { const [_pending, startTransition] = useTransition() const [commentDraft, setCommentDraft] = useState("") const [isCommenting, setCommenting] = useState(false) const [isFocusedOnInput, setFocusedOnInput] = useState(false) // current time in the media // note: this is used to *set* the current time, not to read it // EDIT: you know what, let's do this the dirty way for now // const [desiredCurrentTime, setDesiredCurrentTime] = useState() const { user } = useCurrentUser() const [userThumbnail, setUserThumbnail] = useState("") useEffect(() => { setUserThumbnail(user?.thumbnail || "") }, [user?.thumbnail]) const handleBadUserThumbnail = () => { if (userThumbnail) { setUserThumbnail("") } } const media = useStore(s => s.publicMedia) const mediaId = `${media?.id || ""}` const [copied, setCopied] = useState(false) const [channelThumbnail, setChannelThumbnail] = useState(`${media?.channel.thumbnail || ""}`) const setPublicMedia = useStore(s => s.setPublicMedia) const publicComments = useStore(s => s.publicComments) const setPublicComments = useStore(s => s.setPublicComments) const isEquirectangular = parseMediaProjectionType(media) === "equirectangular" // we inject the current videoId in the URL, if it's not already present // this is a hack for Hugging Face iframes useEffect(() => { const queryString = new URL(location.href).search const searchParams = new URLSearchParams(queryString) if (mediaId) { // TODO: the "v" parameter is legacy, it made sense in the past when // AiTube used to only support traditional videos if (searchParams.get("v") !== mediaId || searchParams.get("m") !== mediaId) { console.log(`current mediaId "${mediaId}" isn't set in the URL query params.. TODO we should set it`) // searchParams.set("m", mediaId) // location.search = searchParams.toString() } } else { // searchParams.delete("m") // location.search = searchParams.toString() } }, [mediaId]) useEffect(() => { if (copied) { setTimeout(() => { setCopied(false) }, 2000) } }, [copied]) const handleBadChannelThumbnail = () => { if (channelThumbnail) { setChannelThumbnail("") } } useEffect(() => { startTransition(async () => { if (!media || !media.id) { return } try { const numberOfViews = await countNewMediaView(mediaId) setPublicMedia({ ...media, numberOfViews }) } catch (err) { console.error(`failed to count the number of view for mediaId ${mediaId}`) } }) }, [media?.id]) useEffect(() => { startTransition(async () => { if (!media || !media.id) { return } const comments = await getComments(mediaId) setPublicComments(comments) }) }, [media?.id]) const [huggingfaceApiKey] = useLocalStorage( localStorageKeys.huggingfaceApiKey, defaultSettings.huggingfaceApiKey ) /* useEffect(() => { window.addEventListener("keydown", function (e) { if (e.code === "Space") { e.preventDefault(); } }) }, []) */ if (!media) { return null } const handleSubmitComment = () => { startTransition(async () => { if (!commentDraft || !huggingfaceApiKey || !mediaId) { return } const limitedSizeComment = commentDraft.trim().slice(0, 1024).trim() const comment = await submitComment(media.id, limitedSizeComment, huggingfaceApiKey) setPublicComments( [comment].concat(publicComments) ) setCommentDraft("") setFocusedOnInput(false) setCommenting(false) }) } return (
{/** AI MEDIA PLAYER - HORIZONTAL */} {/** AI MEDIA TITLE - HORIZONTAL */}
{media.label}
{/*
AI Media Model: {media.model || "HotshotXL"}
*/}
{/** MEDIA TOOLBAR - HORIZONTAL */}
{/** LEFT PART OF THE TOOLBAR */} {/** RIGHT PART OF THE TOOLBAR */}
{/* SHARE */}
setCopied(true)}>
{ copied ? : }
{copied ? "Copied!" : "Share"}
Made with {media.model} {media.model} {isEquirectangular && See in VR } Source
{/** MEDIA DESCRIPTION - VERTICAL */}
{/* DESCRIPTION BLOCK */}
{formatLargeNumber(media.numberOfViews)} views
{formatTimeAgo(media.updatedAt).replace("about ", "")}

{media.description}

{/* COMMENTS */}
{Number(publicComments?.length || 0).toLocaleString()} Comment{ Number(publicComments?.length || 0) === 1 ? '' : 's' }
{/* COMMENT INPUT BLOCK - HORIZONTAL */} {user &&
{/* AVATAR */}
{ userThumbnail ?
: }
{/* COMMENT INPUTS AND BUTTONS - VERTICAL */}
{ if (!isFocusedOnInput) { setFocusedOnInput(true) } if (!isCommenting) { setCommenting(true) } setCommentDraft(x.target.value) }} value={commentDraft} onFocus={() => { if (!isFocusedOnInput) { setFocusedOnInput(true) } if (!isCommenting) { setCommenting(true) } }} onBlur={() => { setFocusedOnInput(false) }} onKeyDown={({ key }) => { if (key === 'Enter') { handleSubmitComment() } else { if (!isFocusedOnInput) { setFocusedOnInput(true) } if (!isCommenting) { setCommenting(true) } } }} />
{ setCommentDraft("") setCommenting(false) setFocusedOnInput(false) }} >Cancel Comment
}
) }