返回 presentation-ai
EmbedRenderer.tsx
1 "use client";
2
3 import Image from "next/image";
4 import { useState } from "react";
5 import LiteYouTubeEmbed from "react-lite-youtube-embed";
6
7 import {
8 extractYouTubeVideoId,
9 generateEmbedUrl,
10 getEmbedConfig,
11 } from "@/components/plate/ui/media-embeds";
12 import { cn } from "@/lib/utils";
13
14 interface EmbedRendererProps {
15 embedType: string;
16 url: string;
17 className?: string;
18 style?: React.CSSProperties;
19 }
20
21 export function EmbedRenderer({
22 embedType,
23 url,
24 className,
25 style,
26 }: EmbedRendererProps) {
27 const [hasError, setHasError] = useState(false);
28 const config = getEmbedConfig(embedType);
29
30 if (!config) {
31 return (
32 <div
33 className={cn(
34 "flex items-center justify-center bg-muted/30 p-4",
35 className,
36 )}
37 style={style}
38 >
39 <p className="text-sm text-muted-foreground">
40 Unsupported embed type: {embedType}
41 </p>
42 </div>
43 );
44 }
45
46 const handleError = () => {
47 setHasError(true);
48 };
49
50 if (hasError) {
51 return (
52 <div
53 className={cn(
54 "flex items-center justify-center bg-muted/30 p-4",
55 className,
56 )}
57 style={style}
58 >
59 <div className="text-center">
60 <p className="mb-2 text-sm text-muted-foreground">
61 Failed to load embed
62 </p>
63 <a
64 href={url}
65 target="_blank"
66 rel="noopener noreferrer"
67 className="text-sm text-blue-600 hover:underline"
68 >
69 Open {config.name} link
70 </a>
71 </div>
72 </div>
73 );
74 }
75
76 if (embedType === "image" || embedType === "infographic") {
77 const embedUrl = generateEmbedUrl(url, embedType);
78 return (
79 <Image
80 unoptimized
81 width={400}
82 height={300}
83 src={embedUrl}
84 alt={config.name}
85 className={cn("h-full w-full object-contain", className)}
86 style={style}
87 onError={handleError}
88 />
89 );
90 }
91
92 // Specia handling for YouTube using LiteYouTubeEmbed
93 if (embedType === "youtube") {
94 const videoId = extractYouTubeVideoId(url);
95 if (!videoId) {
96 return (
97 <div
98 className={cn(
99 "flex items-center justify-center bg-muted/30 p-4",
100 className,
101 )}
102 style={style}
103 >
104 <p className="text-sm text-muted-foreground">Invalid YouTube URL</p>
105 </div>
106 );
107 }
108
109 return (
110 <div
111 className={cn("relative overflow-hidden rounded-md", className)}
112 style={style}
113 >
114 <LiteYouTubeEmbed
115 id={videoId}
116 title="youtube"
117 wrapperClass={cn(
118 "absolute! inset-0! h-full! w-full!",
119 "cursor-pointer bg-black bg-cover bg-center",
120 "[&.lyt-activated]:before:absolute [&.lyt-activated]:before:top-0 [&.lyt-activated]:before:h-15 [&.lyt-activated]:before:w-full [&.lyt-activated]:before:bg-top [&.lyt-activated]:before:bg-repeat-x [&.lyt-activated]:before:pb-12.5 [&.lyt-activated]:before:[transition:all_0.2s_cubic-bezier(0,0,0.2,1)]",
121 "[&.lyt-activated]:before:bg-[url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAADGCAYAAAAT+OqFAAAAdklEQVQoz42QQQ7AIAgEF/T/D+kbq/RWAlnQyyazA4aoAB4FsBSA/bFjuF1EOL7VbrIrBuusmrt4ZZORfb6ehbWdnRHEIiITaEUKa5EJqUakRSaEYBJSCY2dEstQY7AuxahwXFrvZmWl2rh4JZ07z9dLtesfNj5q0FU3A5ObbwAAAABJRU5ErkJggg==)]",
122 "[&_>_iframe]:absolute [&_>_iframe]:top-0 [&_>_iframe]:left-0 [&_>_iframe]:size-full",
123 "[&_>_.lty-playbtn]:z-1 [&_>_.lty-playbtn]:h-11.5 [&_>_.lty-playbtn]:w-17.5 [&_>_.lty-playbtn]:rounded-[14%] [&_>_.lty-playbtn]:bg-[#212121] [&_>_.lty-playbtn]:opacity-80 [&_>_.lty-playbtn]:[transition:all_0.2s_cubic-bezier(0,0,0.2,1)]",
124 "[&:hover_>_.lty-playbtn]:bg-[red] [&:hover_>_.lty-playbtn]:opacity-100",
125 '[&_>_.lty-playbtn]:before:border-y-11 [&_>_.lty-playbtn]:before:border-r-0 [&_>_.lty-playbtn]:before:border-l-19 [&_>_.lty-playbtn]:before:border-[transparent_transparent_transparent_#fff] [&_>_.lty-playbtn]:before:content-[""]',
126 "[&_>_.lty-playbtn]:absolute [&_>_.lty-playbtn]:top-1/2 [&_>_.lty-playbtn]:left-1/2 [&_>_.lty-playbtn]:transform-[translate3d(-50%,-50%,0)]",
127 "[&_>_.lty-playbtn]:before:absolute [&_>_.lty-playbtn]:before:top-1/2 [&_>_.lty-playbtn]:before:left-1/2 [&_>_.lty-playbtn]:before:transform-[translate3d(-50%,-50%,0)]",
128 "[&.lyt-activated]:cursor-[unset]",
129 "[&.lyt-activated]:before:pointer-events-none [&.lyt-activated]:before:opacity-0",
130 "[&.lyt-activated_>_.lty-playbtn]:pointer-events-none [&.lyt-activated_>_.lty-playbtn]:opacity-0!",
131 )}
132 />
133 </div>
134 );
135 }
136
137 // For other embed types, use regular iframe
138 const embedUrl = generateEmbedUrl(url, embedType);
139
140 return (
141 <iframe
142 src={embedUrl}
143 className={cn("h-full w-full border-0", className)}
144 style={style}
145 allowFullScreen
146 allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
147 sandbox="allow-forms allow-scripts allow-popups allow-popups-to-escape-sandbox allow-presentation"
148 onError={handleError}
149 title={`${config.name} embed`}
150 />
151 );
152 }
153
153 lines Plain Text