forked from 77media/video-flow
587 lines
27 KiB
TypeScript
587 lines
27 KiB
TypeScript
'use client';
|
||
|
||
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
||
import { Carousel } from 'antd';
|
||
import type { CarouselRef } from 'antd/es/carousel';
|
||
import { Play, Pause, FeatherIcon, MessageCircleMore, Download, ArrowDownWideNarrow, RotateCcw, Navigation, X } from 'lucide-react';
|
||
import { TaskObject } from '@/api/DTO/movieEdit';
|
||
import { ScriptRenderer } from '@/components/script-renderer/ScriptRenderer';
|
||
import ScriptLoading from './script-loading';
|
||
import { GlassIconButton } from '@/components/ui/glass-icon-button';
|
||
import { downloadVideo, downloadAllVideos, getFirstFrame } from '@/utils/tools';
|
||
import { post } from '@/api/request';
|
||
import { Drawer } from 'antd';
|
||
import { useSearchParams } from 'next/navigation';
|
||
import error_image from '@/public/assets/error.webp';
|
||
import { showDownloadOptionsModal } from './download-options-modal';
|
||
import RenderLoading from './RenderLoading';
|
||
import { useBrowserType } from '@/hooks/useBrowserType';
|
||
|
||
interface H5MediaViewerProps {
|
||
/** 任务对象,包含各阶段数据 */
|
||
taskObject: TaskObject;
|
||
/** 剧本数据(仅剧本阶段使用) */
|
||
scriptData: any;
|
||
/** 当前索引(视频/分镜阶段用于定位) */
|
||
currentSketchIndex: number;
|
||
/** 选中视图:final 或 video,用于覆盖阶段渲染 */
|
||
selectedView?: 'final' | 'video' | null;
|
||
/** 渲染模式(仅剧本阶段透传) */
|
||
mode: string;
|
||
/** 以下为剧本阶段透传必需项(与桌面版保持一致) */
|
||
setIsPauseWorkFlow: (isPause: boolean) => void;
|
||
setAnyAttribute: any;
|
||
isPauseWorkFlow: boolean;
|
||
applyScript: any;
|
||
/** Carousel 切换时回传新的索引 */
|
||
setCurrentSketchIndex: (index: number) => void;
|
||
/** 打开智能对话 */
|
||
onOpenChat?: () => void;
|
||
/** 设置聊天预览视频 */
|
||
setVideoPreview?: (url: string, id: string) => void;
|
||
/** 智能对话是否打开(H5可忽略布局调整,仅占位) */
|
||
isSmartChatBoxOpen?: boolean;
|
||
/** 失败重试生成视频 */
|
||
onRetryVideo?: (video_id: string) => void;
|
||
/** 切换选择视图(final 或 video) */
|
||
onSelectView?: (view: 'final' | 'video') => void;
|
||
/** 启用视频编辑功能 */
|
||
enableVideoEdit?: boolean;
|
||
/** 视频编辑描述提交回调 */
|
||
onVideoEditDescriptionSubmit?: (editPoint: any, description: string) => void;
|
||
/** 项目ID */
|
||
projectId?: string;
|
||
/** 视频比例 */
|
||
aspectRatio?: string;
|
||
/** 是否显示进度条 */
|
||
showProgress?: boolean;
|
||
}
|
||
|
||
/**
|
||
* 面向 H5 的媒体预览组件。
|
||
* - 除剧本阶段外,统一使用 antd Carousel 展示 图片/视频。
|
||
* - 视频仅保留中间的大号播放/暂停按钮。
|
||
*/
|
||
export function H5MediaViewer({
|
||
taskObject,
|
||
scriptData,
|
||
currentSketchIndex,
|
||
selectedView,
|
||
mode,
|
||
setIsPauseWorkFlow,
|
||
setAnyAttribute,
|
||
isPauseWorkFlow,
|
||
applyScript,
|
||
setCurrentSketchIndex,
|
||
onOpenChat,
|
||
setVideoPreview,
|
||
isSmartChatBoxOpen,
|
||
onRetryVideo,
|
||
onSelectView,
|
||
enableVideoEdit,
|
||
onVideoEditDescriptionSubmit,
|
||
projectId,
|
||
aspectRatio,
|
||
showProgress
|
||
}: H5MediaViewerProps) {
|
||
const carouselRef = useRef<CarouselRef>(null);
|
||
const videoRefs = useRef<Array<HTMLVideoElement | null>>([]);
|
||
const rootRef = useRef<HTMLDivElement | null>(null);
|
||
const [activeIndex, setActiveIndex] = useState<number>(0);
|
||
const [isPlaying, setIsPlaying] = useState<boolean>(false);
|
||
const [isCatalogOpen, setIsCatalogOpen] = useState<boolean>(false);
|
||
const browserType = useBrowserType();
|
||
const searchParams = useSearchParams();
|
||
const episodeId = searchParams.get('episodeId') || '';
|
||
/** 解析形如 "16:9" 的比例字符串 */
|
||
const parseAspect = (input?: string): { w: number; h: number } => {
|
||
const parts = (typeof input === 'string' ? input.split(':') : []);
|
||
const w = Number(parts[0]);
|
||
const h = Number(parts[1]);
|
||
return {
|
||
w: Number.isFinite(w) && w > 0 ? w : 16,
|
||
h: Number.isFinite(h) && h > 0 ? h : 9
|
||
};
|
||
};
|
||
|
||
/** 根据浏览器类型获取最大高度值 */
|
||
const maxHeight = useMemo(() => {
|
||
let h = 10.5;
|
||
switch (browserType) {
|
||
case 'Edge':
|
||
h = 10.5;
|
||
break;
|
||
case 'Chrome':
|
||
h = 17;
|
||
break;
|
||
case 'Safari':
|
||
h = 15;
|
||
break;
|
||
default:
|
||
h = 17;
|
||
break;
|
||
}
|
||
if (!showProgress) {
|
||
h = h - 1.5;
|
||
}
|
||
return `calc(100vh - ${h}rem)`;
|
||
}, [browserType, showProgress]);
|
||
|
||
/** 视频轮播容器高度:按 aspectRatio 计算(基于视口宽度) */
|
||
const videoWrapperHeight = useMemo(() => {
|
||
const { w, h } = parseAspect(aspectRatio);
|
||
return `min(calc(100vw * ${h} / ${w}), ${maxHeight})`;
|
||
}, [aspectRatio, maxHeight]);
|
||
|
||
/** 图片轮播容器高度:默认 16:9 */
|
||
const imageWrapperHeight = useMemo(() => {
|
||
const { w, h } = parseAspect(aspectRatio);
|
||
return `min(calc(100vw * ${h} / ${w}), ${maxHeight})`;
|
||
}, [aspectRatio, maxHeight]);
|
||
|
||
// 计算当前阶段类型
|
||
const stage = (selectedView === 'final' && taskObject.final?.url)
|
||
? 'final_video'
|
||
: (!['init', 'script', 'character', 'scene'].includes(taskObject.currentStage) ? 'video' : taskObject.currentStage);
|
||
|
||
// 生成各阶段对应的 slides 数据
|
||
const videoUrls = useMemo(() => {
|
||
if (stage === 'final_video') {
|
||
return taskObject.final?.url ? [taskObject.final.url] : [];
|
||
}
|
||
if (stage === 'video') {
|
||
// 注意:不再过滤,保持与原始数组长度一致,避免索引错位
|
||
const list = (taskObject.videos?.data ?? []) as Array<any>;
|
||
return list.map(v => (Array.isArray(v?.urls) && v.urls.length > 0 ? v.urls[0] : '')) as string[];
|
||
}
|
||
return [];
|
||
}, [stage, taskObject.final?.url, taskObject.videos?.data]);
|
||
|
||
const imageItems = useMemo(() => {
|
||
if (stage === 'scene' || stage === 'character') {
|
||
const roles = (taskObject.roles?.data ?? []) as Array<any>;
|
||
const scenes = (taskObject.scenes?.data ?? []) as Array<any>;
|
||
console.log('h5-media-viewer:stage', stage);
|
||
console.log('h5-media-viewer:roles', roles);
|
||
console.log('h5-media-viewer:scenes', scenes);
|
||
return [...roles, ...scenes].map(item => ({
|
||
url: item?.url as string | undefined,
|
||
status: item?.status as number | undefined,
|
||
type: item?.type as string | undefined,
|
||
}));
|
||
}
|
||
return [] as Array<{ url?: string; status?: number; type?: string }>;
|
||
}, [stage, taskObject.roles?.data, taskObject.scenes?.data]);
|
||
|
||
// 占位,避免未使用警告
|
||
useEffect(() => {
|
||
void isSmartChatBoxOpen;
|
||
}, [isSmartChatBoxOpen]);
|
||
|
||
// 同步外部索引到 Carousel
|
||
useEffect(() => {
|
||
if (stage === 'video' || stage === 'scene' || stage === 'character') {
|
||
const target = Math.max(0, currentSketchIndex);
|
||
carouselRef.current?.goTo(target, false);
|
||
setActiveIndex(target);
|
||
setIsPlaying(false);
|
||
// 切换时暂停全部视频
|
||
videoRefs.current.forEach(v => v?.pause());
|
||
}
|
||
}, [currentSketchIndex, stage]);
|
||
|
||
// 阶段变更时重置状态
|
||
useEffect(() => {
|
||
setActiveIndex(0);
|
||
setIsPlaying(false);
|
||
videoRefs.current.forEach(v => v?.pause());
|
||
}, [stage]);
|
||
|
||
const handleAfterChange = (index: number) => {
|
||
setActiveIndex(index);
|
||
setIsPlaying(false);
|
||
videoRefs.current.forEach(v => v?.pause());
|
||
// 同步到父级索引
|
||
if (stage === 'video' || stage === 'scene' || stage === 'character') {
|
||
setCurrentSketchIndex(index);
|
||
}
|
||
};
|
||
|
||
const togglePlay = () => {
|
||
if (stage !== 'final_video' && stage !== 'video') return;
|
||
const currentVideo = videoRefs.current[activeIndex] ?? null;
|
||
if (!currentVideo) return;
|
||
if (currentVideo.paused) {
|
||
currentVideo.play().then(() => setIsPlaying(true)).catch(() => setIsPlaying(false));
|
||
} else {
|
||
currentVideo.pause();
|
||
setIsPlaying(false);
|
||
}
|
||
};
|
||
|
||
// 渲染视频 slide
|
||
const renderVideoSlides = () => (
|
||
<div data-alt="carousel-wrapper" className="relative w-full aspect-auto min-h-[200px] overflow-hidden rounded-lg" style={{
|
||
height: videoWrapperHeight,
|
||
}}>
|
||
<Carousel
|
||
ref={carouselRef}
|
||
key={`h5-carousel-video-${stage}-${videoUrls.length}`}
|
||
arrows
|
||
dots={false}
|
||
infinite={false}
|
||
afterChange={handleAfterChange}
|
||
className="absolute inset-0"
|
||
slidesToShow={1}
|
||
adaptiveHeight
|
||
>
|
||
{videoUrls.map((url, idx) => {
|
||
const hasUrl = typeof url === 'string' && url.length > 0;
|
||
const raw = (taskObject.videos?.data ?? [])[idx] as any;
|
||
const status = raw?.video_status;
|
||
const videoId = raw?.video_id as string | undefined;
|
||
return (
|
||
<div key={`h5-video-${idx}`} data-alt="video-slide" className="relative w-full h-full flex justify-center">
|
||
{hasUrl ? (
|
||
<>
|
||
<video
|
||
ref={(el) => (videoRefs.current[idx] = el)}
|
||
className="w-full h-full object-contain [transform:translateZ(0)] [backface-visibility:hidden] [will-change:transform] bg-black cursor-pointer"
|
||
src={url}
|
||
preload="metadata"
|
||
playsInline
|
||
loop
|
||
poster={getFirstFrame(url)}
|
||
crossOrigin="anonymous"
|
||
onClick={(e) => {
|
||
e.stopPropagation();
|
||
if (activeIndex === idx) togglePlay();
|
||
}}
|
||
onLoadedMetadata={() => {}}
|
||
onPlay={() => {
|
||
if (activeIndex === idx) setIsPlaying(true);
|
||
}}
|
||
onPause={() => {
|
||
if (activeIndex === idx) setIsPlaying(false);
|
||
}}
|
||
onCanPlay={() => {}}
|
||
onError={() => {}}
|
||
/>
|
||
{/* 顶部功能按钮改为全局固定渲染,移出 slide */}
|
||
{activeIndex === idx && !isPlaying && (
|
||
<button
|
||
type="button"
|
||
onClick={togglePlay}
|
||
data-alt="play-toggle"
|
||
className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 w-16 h-16 rounded-full bg-black/60 text-white flex items-center justify-center active:scale-95"
|
||
aria-label={'Play'}
|
||
>
|
||
<Play className="w-8 h-8" />
|
||
</button>
|
||
)}
|
||
</>
|
||
) : (
|
||
<div className="w-full aspect-auto min-h-[200px] flex items-center justify-center bg-black/10 relative" data-alt="video-status" style={{
|
||
height: videoWrapperHeight,
|
||
}}>
|
||
{(status === 0 || status === 2) && (
|
||
<RenderLoading
|
||
recordSize="120px"
|
||
loadingText={status === 0 ? 'Generating video...' : 'Generate failed'}
|
||
isFailed={status === 2}
|
||
/>
|
||
)}
|
||
{/* {status === 2 && (
|
||
<div className="flex flex-col items-center justify-center gap-1">
|
||
<img src={error_image.src} alt="error" className="w-12 h-12" />
|
||
<span className="text-[#fa485f] text-base">Generate failed</span>
|
||
</div>
|
||
)} */}
|
||
</div>
|
||
)}
|
||
</div>
|
||
);
|
||
})}
|
||
</Carousel>
|
||
</div>
|
||
);
|
||
|
||
// 渲染图片 slide
|
||
const renderImageSlides = () => (
|
||
<div data-alt="carousel-wrapper" className="relative w-full aspect-auto min-h-[200px] overflow-hidden rounded-lg" style={{
|
||
height: imageWrapperHeight,
|
||
}}>
|
||
<Carousel
|
||
ref={carouselRef}
|
||
key={`h5-carousel-image-${stage}-${imageItems.length}`}
|
||
arrows
|
||
dots={false}
|
||
infinite={false}
|
||
afterChange={handleAfterChange}
|
||
className="absolute inset-0"
|
||
slidesToShow={1}
|
||
adaptiveHeight
|
||
>
|
||
{imageItems.map((item, idx) => {
|
||
const status = item?.status;
|
||
const url = item?.url;
|
||
const showImage = status === 1 && typeof url === 'string' && url.length > 0;
|
||
return (
|
||
<div key={`h5-image-${idx}`} data-alt="image-slide" className="relative w-full h-full flex justify-center">
|
||
{showImage ? (
|
||
<img src={url} alt="scene" className="w-full h-full object-contain" />
|
||
) : (
|
||
<div className="w-full aspect-auto min-h-[200px] flex items-center justify-center bg-black/10 relative" data-alt="image-status" style={{
|
||
height: imageWrapperHeight,
|
||
}}>
|
||
{(status === 0 || status === 2) && (
|
||
<RenderLoading
|
||
recordSize="120px"
|
||
loadingText={status === 0 ? (item.type === 'role' ? 'Generating role...' : 'Generating scene...') : 'Generate failed'}
|
||
isFailed={status === 2}
|
||
/>
|
||
)}
|
||
{/* {status === 2 && (
|
||
<div className="flex flex-col items-center justify-center gap-1">
|
||
<img src={error_image.src} alt="error" className="w-12 h-12" />
|
||
<span className="text-[#fa485f] text-base">Generate failed</span>
|
||
</div>
|
||
)} */}
|
||
</div>
|
||
)}
|
||
</div>
|
||
);
|
||
})}
|
||
</Carousel>
|
||
</div>
|
||
);
|
||
|
||
// 剧本阶段:不使用 Carousel,沿用 ScriptRenderer
|
||
if (stage === 'script') {
|
||
const navItems = Array.isArray(scriptData) ? (scriptData as Array<any>).map(v => ({ id: v?.id, title: v?.title })) : [];
|
||
const scrollToSection = (id?: string) => {
|
||
if (!id) return;
|
||
const el = document.getElementById(`section-${id}`);
|
||
if (el) {
|
||
el.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||
}
|
||
};
|
||
return (
|
||
<div data-alt="script-content" className="w-full overflow-auto"
|
||
style={{
|
||
height: 'calc(100vh - 10rem)'
|
||
}}>
|
||
{scriptData ? (
|
||
<>
|
||
<ScriptRenderer
|
||
data={scriptData}
|
||
setIsPauseWorkFlow={setIsPauseWorkFlow}
|
||
setAnyAttribute={setAnyAttribute}
|
||
isPauseWorkFlow={isPauseWorkFlow}
|
||
applyScript={applyScript}
|
||
mode={mode}
|
||
from="h5"
|
||
/>
|
||
<button
|
||
type="button"
|
||
data-alt="open-catalog-button"
|
||
className="fixed bottom-[5rem] right-4 z-[60] w-12 h-12 rounded-full bg-gradient-to-br from-blue-500 to-blue-600 text-white shadow-lg flex items-center justify-center active:scale-95"
|
||
aria-label="open-catalog"
|
||
onClick={() => setIsCatalogOpen(true)}
|
||
>
|
||
<Navigation className="w-6 h-6" />
|
||
</button>
|
||
|
||
<Drawer
|
||
open={isCatalogOpen}
|
||
onClose={() => setIsCatalogOpen(false)}
|
||
placement="right"
|
||
width={'auto'}
|
||
mask
|
||
maskClosable={true}
|
||
maskStyle={{ backgroundColor: 'rgba(0,0,0,0)' }}
|
||
className="[&_.ant-drawer-content-wrapper]:w-auto [&_.ant-drawer-content-wrapper]:max-w-[80vw] backdrop-blur-lg bg-black/30 border border-white/20 shadow-xl"
|
||
rootClassName="outline-none"
|
||
data-alt="catalog-drawer"
|
||
closable={false}
|
||
style={{
|
||
backgroundColor: 'transparent',
|
||
borderBottomLeftRadius: 10,
|
||
borderTopLeftRadius: 10,
|
||
overflow: 'hidden',
|
||
}}
|
||
styles={{
|
||
body: {
|
||
backgroundColor: 'transparent',
|
||
padding: 0,
|
||
},
|
||
}}
|
||
>
|
||
<div className="mt-1" data-alt="catalog-list">
|
||
<div className="px-3 py-2 text-blue-500 text-xl font-bold">navigation</div>
|
||
{navItems.map(item => (
|
||
<div
|
||
key={item.id}
|
||
role="button"
|
||
tabIndex={0}
|
||
onClick={() => {
|
||
scrollToSection(item.id);
|
||
setIsCatalogOpen(false);
|
||
}}
|
||
onKeyDown={(e) => {
|
||
if (e.key === 'Enter') {
|
||
scrollToSection(item.id);
|
||
setIsCatalogOpen(false);
|
||
}
|
||
}}
|
||
className="px-3 py-2 text-white/90 text-sm cursor-pointer transition-colors"
|
||
data-alt="catalog-item"
|
||
>
|
||
{item.title}
|
||
</div>
|
||
))}
|
||
</div>
|
||
</Drawer>
|
||
</>
|
||
|
||
) : (
|
||
<ScriptLoading isCompleted={!!scriptData} />
|
||
)}
|
||
|
||
|
||
</div>
|
||
);
|
||
}
|
||
|
||
// 其他阶段:使用 Carousel
|
||
return (
|
||
<div ref={rootRef} data-alt="h5-media-viewer" className={`relative`}
|
||
style={{
|
||
width: 'calc(100vw - 2rem)'
|
||
}}>
|
||
{stage === 'final_video' && videoUrls.length > 0 && renderVideoSlides()}
|
||
{stage === 'video' && videoUrls.length > 0 && renderVideoSlides()}
|
||
{(stage === 'scene' || stage === 'character') && imageItems.length > 0 && renderImageSlides()}
|
||
{/* 全局固定操作区 */}
|
||
{(stage === 'video' || stage === 'final_video') && (
|
||
<div data-alt="global-video-actions" className="absolute top-0 right-4 z-[60] flex flex-col items-center gap-2">
|
||
{stage === 'video' && (
|
||
<>
|
||
<GlassIconButton
|
||
data-alt="edit-with-chat-button"
|
||
className="w-8 h-8 bg-custom-purple backdrop-blur-xl rounded-full flex items-center justify-center transition-all"
|
||
icon={FeatherIcon}
|
||
size="sm"
|
||
aria-label="edit-with-chat"
|
||
onClick={() => {
|
||
const current = (taskObject.videos?.data ?? [])[activeIndex] as any;
|
||
if (current && Array.isArray(current.urls) && current.urls.length > 0 && setVideoPreview) {
|
||
setVideoPreview(current.urls[0], current.video_id);
|
||
onOpenChat && onOpenChat();
|
||
}
|
||
}}
|
||
/>
|
||
<GlassIconButton
|
||
data-alt="download-button"
|
||
className="w-8 h-8 bg-gradient-to-br from-purple-500/80 to-purple-600/80 backdrop-blur-xl rounded-full flex items-center justify-center hover:from-purple-400/80 hover:to-purple-500/80 transition-all"
|
||
icon={Download}
|
||
size="sm"
|
||
aria-label="download"
|
||
onClick={() => {
|
||
const current = (taskObject.videos?.data ?? [])[activeIndex] as any;
|
||
const status = current?.video_status;
|
||
const hasUrl = current && Array.isArray(current.urls) && current.urls.length > 0;
|
||
const hasFinalVideo = taskObject.final?.url;
|
||
const baseVideoCount = (taskObject.videos?.data ?? []).length;
|
||
const totalVideos = hasFinalVideo ? baseVideoCount + 1 : baseVideoCount;
|
||
const isCurrentVideoFailed = status === 2;
|
||
|
||
showDownloadOptionsModal({
|
||
currentVideoIndex: hasFinalVideo ? activeIndex + 1 : activeIndex,
|
||
totalVideos,
|
||
isCurrentVideoFailed,
|
||
projectId: episodeId,
|
||
videoId: current?.video_id,
|
||
onDownloadCurrent: async (withWatermark: boolean) => {
|
||
if (!current?.video_id) return;
|
||
const json: any = await post('/movie/download_video', {
|
||
project_id: episodeId,
|
||
video_id: current.video_id,
|
||
watermark: withWatermark
|
||
});
|
||
const url = json?.data?.download_url as string | undefined;
|
||
if (url) await downloadVideo(url);
|
||
},
|
||
onDownloadAll: ()=>{}
|
||
});
|
||
}}
|
||
/>
|
||
{(() => {
|
||
const status = (taskObject.videos?.data ?? [])[activeIndex]?.video_status;
|
||
return status === 2 ? (
|
||
<GlassIconButton
|
||
data-alt="retry-button"
|
||
className="w-8 h-8 bg-gradient-to-br from-purple-500/80 to-purple-600/80 backdrop-blur-xl border border-purple-400/30 rounded-full flex items-center justify-center hover:from-purple-400/80 hover:to-purple-500/80 transition-all"
|
||
icon={RotateCcw}
|
||
size="sm"
|
||
aria-label="retry"
|
||
onClick={() => {
|
||
const vid = (taskObject.videos?.data ?? [])[activeIndex]?.video_id;
|
||
if (vid && onRetryVideo) onRetryVideo(vid);
|
||
}}
|
||
/>
|
||
) : null;
|
||
})()}
|
||
</>
|
||
)}
|
||
{stage === 'final_video' && (
|
||
<GlassIconButton
|
||
data-alt="download-button"
|
||
className="w-8 h-8 bg-gradient-to-br from-purple-500/80 to-purple-600/80 backdrop-blur-xl rounded-full flex items-center justify-center hover:from-purple-400/80 hover:to-purple-500/80 transition-all"
|
||
icon={Download}
|
||
size="sm"
|
||
aria-label="download"
|
||
onClick={() => {
|
||
const totalVideos = (taskObject.videos?.data ?? []).length + 1;
|
||
const finalUrl = videoUrls[0];
|
||
|
||
showDownloadOptionsModal({
|
||
currentVideoIndex: 0,
|
||
totalVideos,
|
||
isCurrentVideoFailed: false,
|
||
isFinalStage: true,
|
||
projectId: episodeId,
|
||
onDownloadCurrent: async (withWatermark: boolean) => {
|
||
const json: any = await post('/movie/download_video', {
|
||
project_id: episodeId,
|
||
watermark: withWatermark
|
||
});
|
||
const url = json?.data?.download_url as string | undefined;
|
||
if (url) await downloadVideo(url);
|
||
},
|
||
onDownloadAll: ()=>{}
|
||
});
|
||
}}
|
||
/>
|
||
)}
|
||
</div>
|
||
)}
|
||
<style jsx global>{`
|
||
[data-alt='carousel-wrapper'] .slick-slide { width: 100%;display: flex !important;justify-content: center; }
|
||
.slick-slider { height: 100% !important; }
|
||
.slick-current > div { width: 100% !important; }
|
||
.ant-carousel { height: 100% !important; }
|
||
.slick-list { width: 100%;height: 100% !important;max-height: 100%; }
|
||
.slick-track { display: flex !important; align-items: center;height: 100% !important; }
|
||
[data-alt='carousel-wrapper'] .slick-arrow { z-index: 70 !important; }
|
||
[data-alt='carousel-wrapper'] .slick-prev { left: 8px; }
|
||
[data-alt='carousel-wrapper'] .slick-next { right: 8px; }
|
||
`}</style>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
export default H5MediaViewer;
|
||
|
||
|