feat: update fixing glitch swiper spit
This commit is contained in:
parent
b7a2b445a9
commit
78497bfcb1
|
|
@ -47,6 +47,7 @@ import {
|
|||
|
||||
// Swiper
|
||||
import { Swiper, SwiperSlide } from "swiper/react";
|
||||
import { Swiper as SwiperType } from "swiper";
|
||||
import "swiper/css";
|
||||
import "swiper/css/free-mode";
|
||||
import "swiper/css/navigation";
|
||||
|
|
@ -206,7 +207,7 @@ export default function FormConvertSPIT() {
|
|||
|
||||
// <-- changed: detailThumb is now FileType[] (objects from API) -->
|
||||
const [detailThumb, setDetailThumb] = useState<FileType[]>([]);
|
||||
const [thumbsSwiper, setThumbsSwiper] = useState<any>(null);
|
||||
const [thumbsSwiper, setThumbsSwiper] = useState<SwiperType | null>(null);
|
||||
const [files, setFiles] = useState<FileType[]>([]);
|
||||
const [filePlacements, setFilePlacements] = useState<string[][]>([]);
|
||||
const [articleIds, setArticleIds] = useState<string[]>([]);
|
||||
|
|
@ -229,6 +230,13 @@ export default function FormConvertSPIT() {
|
|||
checkUserPermissions();
|
||||
}, [userLevelId, roleId]);
|
||||
|
||||
// Handle Swiper re-initialization when detailThumb changes
|
||||
useEffect(() => {
|
||||
if (thumbsSwiper && detailThumb.length > 0) {
|
||||
thumbsSwiper.update();
|
||||
}
|
||||
}, [detailThumb, thumbsSwiper]);
|
||||
|
||||
const initializeComponent = async () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
|
|
@ -282,7 +290,10 @@ export default function FormConvertSPIT() {
|
|||
try {
|
||||
const response = await getTagsBySubCategoryId(categoryId);
|
||||
if (response?.data?.data?.length > 0) {
|
||||
const tagsMerge = [...tags, response?.data?.data];
|
||||
// Extract tag names from the response objects
|
||||
const apiTags = response?.data?.data?.map((tag: any) => tag.tagName);
|
||||
// Merge with existing tags, ensuring uniqueness
|
||||
const tagsMerge = Array.from(new Set([...tags, ...apiTags]));
|
||||
setTags(tagsMerge);
|
||||
}
|
||||
} catch (error) {
|
||||
|
|
@ -332,16 +343,16 @@ export default function FormConvertSPIT() {
|
|||
(details as any).contentRewriteDescription || ""
|
||||
);
|
||||
|
||||
// Set category and load category tags
|
||||
if (details.categoryId) {
|
||||
setSelectedCategoryId(details.categoryId);
|
||||
await loadTags(details.categoryId);
|
||||
}
|
||||
|
||||
// Set content tags and merge with category tags
|
||||
if (details.contentTag) {
|
||||
const initialTags = details.contentTag
|
||||
.split(",")
|
||||
.map((tag: string) => tag.trim());
|
||||
setTags(initialTags);
|
||||
const contentTags = details.contentTag.split(",").map((tag: string) => tag.trim());
|
||||
setTags(prev => Array.from(new Set([...prev, ...contentTags])));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to load detail:", error);
|
||||
|
|
@ -544,7 +555,6 @@ export default function FormConvertSPIT() {
|
|||
|
||||
const getPlacementData = () => {
|
||||
const placementData: PlacementData[] = [];
|
||||
console.log("filePlacements : ", filePlacements);
|
||||
for (let i = 0; i < filePlacements.length; i++) {
|
||||
if (filePlacements[i].length > 0) {
|
||||
const placements = filePlacements[i];
|
||||
|
|
@ -950,68 +960,114 @@ export default function FormConvertSPIT() {
|
|||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-4 overflow-hidden">
|
||||
<Swiper
|
||||
key={`main-swiper-${detailThumb.length}`}
|
||||
thumbs={{ swiper: thumbsSwiper }}
|
||||
modules={[FreeMode, Navigation, Thumbs]}
|
||||
navigation={true}
|
||||
className="w-full h-96"
|
||||
spaceBetween={10}
|
||||
loop={false}
|
||||
watchSlidesProgress={true}
|
||||
allowTouchMove={true}
|
||||
resistance={true}
|
||||
resistanceRatio={0.85}
|
||||
preventClicks={false}
|
||||
preventClicksPropagation={false}
|
||||
slidesPerView={1}
|
||||
centeredSlides={true}
|
||||
>
|
||||
{detailThumb.map((item) => (
|
||||
<SwiperSlide key={item.contentId}>
|
||||
<SwiperSlide key={item.contentId} className="!w-full">
|
||||
{item.contentType === "VIDEO" ? (
|
||||
<div className="relative max-h-screen overflow-hidden">
|
||||
<div className="w-full max-h-screen aspect-video">
|
||||
<div className="w-full h-full object-contain">
|
||||
{/* main video player */}
|
||||
<div className="relative w-full h-96 overflow-hidden rounded-lg">
|
||||
<video
|
||||
className="object-contain h-full w-full rounded-lg"
|
||||
className="w-full h-full object-contain rounded-lg"
|
||||
src={item.contentFile}
|
||||
controls
|
||||
// playsInline to better on mobile
|
||||
playsInline
|
||||
// you can set poster if available: poster={item.thumbnailFileUrl}
|
||||
poster={item.thumbnailFileUrl || undefined}
|
||||
title={`Video ${item.contentId}`}
|
||||
onError={(e) => {
|
||||
console.error('Video load error:', e);
|
||||
e.currentTarget.style.display = 'none';
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="w-full h-96 flex items-center justify-center overflow-hidden rounded-lg">
|
||||
<img
|
||||
src={item.contentFile}
|
||||
alt={`Media ${item.contentId}`}
|
||||
className="w-full h-full object-cover rounded-lg"
|
||||
className="max-w-full max-h-full object-contain rounded-lg"
|
||||
style={{ maxWidth: '100%', maxHeight: '100%' }}
|
||||
onError={(e) => {
|
||||
console.error('Image load error:', e);
|
||||
e.currentTarget.style.display = 'none';
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</SwiperSlide>
|
||||
))}
|
||||
</Swiper>
|
||||
|
||||
<Swiper
|
||||
key={`thumbs-swiper-${detailThumb.length}`}
|
||||
onSwiper={setThumbsSwiper}
|
||||
slidesPerView={8}
|
||||
spaceBetween={8}
|
||||
modules={[Pagination, Thumbs]}
|
||||
freeMode={true}
|
||||
watchSlidesProgress={true}
|
||||
modules={[FreeMode, Thumbs]}
|
||||
breakpoints={{
|
||||
320: {
|
||||
slidesPerView: 3,
|
||||
spaceBetween: 4,
|
||||
},
|
||||
640: {
|
||||
slidesPerView: 5,
|
||||
spaceBetween: 6,
|
||||
},
|
||||
768: {
|
||||
slidesPerView: 6,
|
||||
spaceBetween: 8,
|
||||
},
|
||||
1024: {
|
||||
slidesPerView: 8,
|
||||
spaceBetween: 8,
|
||||
},
|
||||
}}
|
||||
className="w-full"
|
||||
loop={false}
|
||||
allowTouchMove={true}
|
||||
resistance={true}
|
||||
resistanceRatio={0.85}
|
||||
preventClicks={false}
|
||||
preventClicksPropagation={false}
|
||||
>
|
||||
{detailThumb.map((item) => (
|
||||
<SwiperSlide key={`thumb-${item.contentId}`}>
|
||||
<SwiperSlide key={`thumb-${item.contentId}`} className="!w-auto flex-shrink-0">
|
||||
{item.contentType === "VIDEO" ? (
|
||||
<div className="relative w-full h-16 rounded cursor-pointer overflow-hidden">
|
||||
<div className="relative w-20 h-16 rounded cursor-pointer overflow-hidden flex-shrink-0">
|
||||
{/* use preload metadata so browser doesn't download full video */}
|
||||
<video
|
||||
src={item.contentFile}
|
||||
className="w-full h-16 object-cover"
|
||||
className="w-full h-full object-cover"
|
||||
muted
|
||||
preload="metadata"
|
||||
playsInline
|
||||
// no controls in thumbnail
|
||||
tabIndex={-1}
|
||||
onError={(e) => {
|
||||
console.error('Thumbnail video load error:', e);
|
||||
e.currentTarget.style.display = 'none';
|
||||
}}
|
||||
/>
|
||||
<div className="absolute inset-0 flex items-center justify-center bg-black/30 pointer-events-none">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="w-6 h-6 text-white"
|
||||
className="w-4 h-4 text-white"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
|
|
@ -1020,11 +1076,17 @@ export default function FormConvertSPIT() {
|
|||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="w-20 h-16 rounded cursor-pointer overflow-hidden flex-shrink-0">
|
||||
<img
|
||||
src={item.contentFile}
|
||||
alt={`Thumbnail ${item.contentId}`}
|
||||
className="w-full h-16 object-cover rounded cursor-pointer"
|
||||
className="w-full h-full object-cover"
|
||||
onError={(e) => {
|
||||
console.error('Thumbnail image load error:', e);
|
||||
e.currentTarget.style.display = 'none';
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</SwiperSlide>
|
||||
))}
|
||||
|
|
|
|||
Loading…
Reference in New Issue