mirror of
https://github.com/mediacms-io/mediacms.git
synced 2025-11-06 07:28:53 -05:00
Enhance end screen overlay for mobile landscape
Improves the EndScreenOverlay to support a 2x2 grid swiper layout for mobile landscape orientation, with dynamic grid configuration and responsive styling. Refactors grid and swiper logic to handle multi-row layouts, adjusts item and overlay styles for different view modes, and updates indicator logic for paginated grids.
This commit is contained in:
parent
4e6ea77448
commit
d60dd66d17
@ -45,7 +45,7 @@ class EndScreenOverlay extends Component {
|
||||
}
|
||||
|
||||
createGrid() {
|
||||
const { columns, maxVideos, useSwiper } = this.getGridConfig();
|
||||
const { columns, maxVideos, useSwiper, itemsPerView, gridRows } = this.getGridConfig();
|
||||
|
||||
// Get videos to show - access directly from options during createEl
|
||||
const relatedVideos = this.options_?.relatedVideos || this.relatedVideos || [];
|
||||
@ -55,7 +55,7 @@ class EndScreenOverlay extends Component {
|
||||
: this.createSampleVideos().slice(0, maxVideos);
|
||||
|
||||
if (useSwiper) {
|
||||
return this.createSwiperGrid(videosToShow);
|
||||
return this.createSwiperGrid(videosToShow, itemsPerView || 2, columns, gridRows || 1);
|
||||
} else {
|
||||
return this.createRegularGrid(columns, videosToShow);
|
||||
}
|
||||
@ -91,14 +91,14 @@ class EndScreenOverlay extends Component {
|
||||
return grid;
|
||||
}
|
||||
|
||||
createSwiperGrid(videosToShow) {
|
||||
createSwiperGrid(videosToShow, itemsPerView = 2, columns = 2, gridRows = 1) {
|
||||
const container = videojs.dom.createEl('div', {
|
||||
className: 'vjs-related-videos-swiper-container',
|
||||
});
|
||||
|
||||
// Container styling - ensure it stays within bounds
|
||||
container.style.position = 'relative';
|
||||
container.style.padding = '20px';
|
||||
container.style.padding = gridRows > 1 ? '12px' : '20px'; // Minimal padding for 2x2 grid
|
||||
container.style.height = '100%';
|
||||
container.style.width = '100%';
|
||||
container.style.display = 'flex';
|
||||
@ -111,16 +111,32 @@ class EndScreenOverlay extends Component {
|
||||
className: 'vjs-related-videos-swiper',
|
||||
});
|
||||
|
||||
swiperWrapper.style.display = 'flex';
|
||||
swiperWrapper.style.overflowX = 'auto';
|
||||
swiperWrapper.style.overflowY = 'hidden';
|
||||
swiperWrapper.style.gap = '12px';
|
||||
swiperWrapper.style.paddingBottom = '10px';
|
||||
swiperWrapper.style.scrollBehavior = 'smooth';
|
||||
swiperWrapper.style.scrollSnapType = 'x mandatory';
|
||||
swiperWrapper.style.width = '100%';
|
||||
swiperWrapper.style.maxWidth = '100%';
|
||||
swiperWrapper.style.boxSizing = 'border-box';
|
||||
if (gridRows > 1) {
|
||||
// Multi-row grid layout (e.g., 2x2 for landscape)
|
||||
swiperWrapper.style.display = 'flex';
|
||||
swiperWrapper.style.overflowX = 'auto';
|
||||
swiperWrapper.style.overflowY = 'hidden';
|
||||
swiperWrapper.style.scrollBehavior = 'smooth';
|
||||
swiperWrapper.style.scrollSnapType = 'x mandatory';
|
||||
swiperWrapper.style.width = '100%';
|
||||
swiperWrapper.style.maxWidth = '100%';
|
||||
swiperWrapper.style.height = '100%';
|
||||
swiperWrapper.style.flex = '1';
|
||||
swiperWrapper.style.boxSizing = 'border-box';
|
||||
swiperWrapper.style.gap = '0'; // Remove gap, we'll handle it in pages
|
||||
} else {
|
||||
// Single row layout (original swiper)
|
||||
swiperWrapper.style.display = 'flex';
|
||||
swiperWrapper.style.overflowX = 'auto';
|
||||
swiperWrapper.style.overflowY = 'hidden';
|
||||
swiperWrapper.style.gap = '12px';
|
||||
swiperWrapper.style.paddingBottom = '10px';
|
||||
swiperWrapper.style.scrollBehavior = 'smooth';
|
||||
swiperWrapper.style.scrollSnapType = 'x mandatory';
|
||||
swiperWrapper.style.width = '100%';
|
||||
swiperWrapper.style.maxWidth = '100%';
|
||||
swiperWrapper.style.boxSizing = 'border-box';
|
||||
}
|
||||
|
||||
// Hide scrollbar and prevent scroll propagation
|
||||
swiperWrapper.style.scrollbarWidth = 'none'; // Firefox
|
||||
@ -158,17 +174,56 @@ class EndScreenOverlay extends Component {
|
||||
{ passive: true }
|
||||
);
|
||||
|
||||
// Create video items for swiper (show 2 at a time, but allow scrolling through all)
|
||||
videosToShow.forEach((video) => {
|
||||
const videoItem = this.createVideoItem(video, true); // Pass true for swiper mode
|
||||
swiperWrapper.appendChild(videoItem);
|
||||
});
|
||||
if (gridRows > 1) {
|
||||
// Create pages with grid layout (e.g., 2x2 grid per page)
|
||||
const itemsPerPage = itemsPerView;
|
||||
const totalPages = Math.ceil(videosToShow.length / itemsPerPage);
|
||||
|
||||
for (let pageIndex = 0; pageIndex < totalPages; pageIndex++) {
|
||||
const page = videojs.dom.createEl('div', {
|
||||
className: 'vjs-swiper-page',
|
||||
});
|
||||
|
||||
page.style.minWidth = '100%';
|
||||
page.style.width = '100%';
|
||||
page.style.height = '100%';
|
||||
page.style.display = 'grid';
|
||||
page.style.gridTemplateColumns = `repeat(${columns}, 1fr)`;
|
||||
page.style.gridTemplateRows = `repeat(${gridRows}, 1fr)`;
|
||||
page.style.gap = '8px';
|
||||
page.style.scrollSnapAlign = 'start';
|
||||
page.style.boxSizing = 'border-box';
|
||||
page.style.alignContent = 'stretch';
|
||||
page.style.justifyContent = 'stretch';
|
||||
page.style.alignItems = 'stretch';
|
||||
page.style.justifyItems = 'stretch';
|
||||
|
||||
// Get videos for this page
|
||||
const startIndex = pageIndex * itemsPerPage;
|
||||
const endIndex = Math.min(startIndex + itemsPerPage, videosToShow.length);
|
||||
const pageVideos = videosToShow.slice(startIndex, endIndex);
|
||||
|
||||
// Create video items for this page
|
||||
pageVideos.forEach((video) => {
|
||||
const videoItem = this.createVideoItem(video, true, itemsPerView, true); // Pass true for grid mode
|
||||
page.appendChild(videoItem);
|
||||
});
|
||||
|
||||
swiperWrapper.appendChild(page);
|
||||
}
|
||||
} else {
|
||||
// Single row - create video items directly
|
||||
videosToShow.forEach((video) => {
|
||||
const videoItem = this.createVideoItem(video, true, itemsPerView, false);
|
||||
swiperWrapper.appendChild(videoItem);
|
||||
});
|
||||
}
|
||||
|
||||
container.appendChild(swiperWrapper);
|
||||
|
||||
// Add navigation indicators if there are more than 2 videos
|
||||
if (videosToShow.length > 2) {
|
||||
const indicators = this.createSwiperIndicators(videosToShow.length, swiperWrapper);
|
||||
// Add navigation indicators if there are more videos than can fit in one view
|
||||
if (videosToShow.length > itemsPerView) {
|
||||
const indicators = this.createSwiperIndicators(videosToShow.length, swiperWrapper, itemsPerView);
|
||||
container.appendChild(indicators);
|
||||
}
|
||||
|
||||
@ -190,10 +245,41 @@ class EndScreenOverlay extends Component {
|
||||
// Calculate maximum rows that can fit - be more aggressive
|
||||
const maxRows = Math.max(2, Math.floor((availableHeight + gap) / (cardHeight + gap)));
|
||||
|
||||
console.log('Grid Config:', { playerWidth, playerHeight, availableHeight, maxRows });
|
||||
// Detect landscape orientation on mobile
|
||||
// Check screen/window orientation first, then player dimensions
|
||||
const screenWidth = window.innerWidth || document.documentElement.clientWidth;
|
||||
const screenHeight = window.innerHeight || document.documentElement.clientHeight;
|
||||
const isScreenLandscape = screenWidth > screenHeight;
|
||||
|
||||
const isLandscape = playerWidth > playerHeight;
|
||||
const isMobile = playerWidth < 700;
|
||||
|
||||
// For mobile, prioritize screen orientation over player dimensions
|
||||
// Only consider it landscape if BOTH screen and player are in landscape
|
||||
const isDefinitelyLandscape = isMobile ? isScreenLandscape && isLandscape : isLandscape;
|
||||
|
||||
console.log('Grid Config:', {
|
||||
screenWidth,
|
||||
screenHeight,
|
||||
isScreenLandscape,
|
||||
playerWidth,
|
||||
playerHeight,
|
||||
availableHeight,
|
||||
maxRows,
|
||||
isLandscape,
|
||||
isDefinitelyLandscape,
|
||||
isMobile,
|
||||
});
|
||||
|
||||
// Enhanced grid configuration to fill all available space
|
||||
if (playerWidth >= 1600) {
|
||||
// Check mobile conditions first to prevent being caught by desktop breakpoints
|
||||
if (isMobile && isDefinitelyLandscape) {
|
||||
// Mobile landscape: show 2x2 grid (4 items total) with swiper for pagination
|
||||
return { columns: 2, maxVideos: 12, useSwiper: true, itemsPerView: 4, gridRows: 2 };
|
||||
} else if (isMobile) {
|
||||
// Mobile portrait: show 2 items in single row swiper mode
|
||||
return { columns: 2, maxVideos: 12, useSwiper: true, itemsPerView: 2, gridRows: 1 };
|
||||
} else if (playerWidth >= 1600) {
|
||||
const columns = 5;
|
||||
return { columns, maxVideos: columns * maxRows, useSwiper: false }; // Fill all available rows
|
||||
} else if (playerWidth >= 1200) {
|
||||
@ -202,11 +288,9 @@ class EndScreenOverlay extends Component {
|
||||
} else if (playerWidth >= 900) {
|
||||
const columns = 3;
|
||||
return { columns, maxVideos: columns * maxRows, useSwiper: false }; // Fill all available rows
|
||||
} else if (playerWidth >= 700) {
|
||||
} else {
|
||||
const columns = 2;
|
||||
return { columns, maxVideos: columns * maxRows, useSwiper: false }; // Fill all available rows
|
||||
} else {
|
||||
return { columns: 2, maxVideos: 12, useSwiper: true }; // Use swiper for small screens
|
||||
}
|
||||
}
|
||||
|
||||
@ -220,7 +304,7 @@ class EndScreenOverlay extends Component {
|
||||
return this.createSampleVideos().slice(0, maxVideos);
|
||||
}
|
||||
|
||||
createVideoItem(video, isSwiperMode = false) {
|
||||
createVideoItem(video, isSwiperMode = false, itemsPerView = 2, isGridMode = false) {
|
||||
const item = videojs.dom.createEl('div', {
|
||||
className: `vjs-related-video-item ${isSwiperMode ? 'vjs-swiper-item' : ''}`,
|
||||
});
|
||||
@ -228,7 +312,7 @@ class EndScreenOverlay extends Component {
|
||||
// Consistent item styling with fixed dimensions
|
||||
item.style.position = 'relative';
|
||||
item.style.backgroundColor = '#1a1a1a';
|
||||
item.style.borderRadius = '6px';
|
||||
item.style.borderRadius = '8px';
|
||||
item.style.overflow = 'hidden';
|
||||
item.style.cursor = 'pointer';
|
||||
item.style.transition = 'transform 0.15s ease, box-shadow 0.15s ease';
|
||||
@ -236,11 +320,21 @@ class EndScreenOverlay extends Component {
|
||||
item.style.flexDirection = 'column';
|
||||
|
||||
// Consistent dimensions for all cards
|
||||
if (isSwiperMode) {
|
||||
// Calculate proper width for swiper items (2 items visible + gap)
|
||||
item.style.minWidth = 'calc(50% - 6px)'; // 50% width minus half the gap
|
||||
item.style.width = 'calc(50% - 6px)';
|
||||
item.style.maxWidth = '180px'; // Maximum width for larger screens
|
||||
if (isGridMode) {
|
||||
// Grid mode (2x2): items fill their grid cell completely
|
||||
item.style.height = '100%';
|
||||
item.style.minHeight = '0';
|
||||
item.style.width = '100%';
|
||||
item.style.maxWidth = 'none';
|
||||
item.style.flex = '1';
|
||||
} else if (isSwiperMode) {
|
||||
// Single row swiper mode: calculate width based on items per view
|
||||
// Formula: (100% / itemsPerView) - (gap * (itemsPerView - 1) / itemsPerView)
|
||||
const itemsPerRow = itemsPerView / (itemsPerView === 4 ? 2 : 1); // For 4 items in 2 rows, show 2 per row
|
||||
const gapAdjustment = (12 * (itemsPerRow - 1)) / itemsPerRow;
|
||||
item.style.minWidth = `calc(${100 / itemsPerRow}% - ${gapAdjustment}px)`;
|
||||
item.style.width = `calc(${100 / itemsPerRow}% - ${gapAdjustment}px)`;
|
||||
item.style.maxWidth = itemsPerView === 4 ? '150px' : '180px'; // Smaller max width for 4 items
|
||||
|
||||
// Simpler height since text is overlaid on thumbnail
|
||||
const cardHeight = '120px'; // Just the thumbnail height
|
||||
@ -270,7 +364,7 @@ class EndScreenOverlay extends Component {
|
||||
}
|
||||
|
||||
// Create thumbnail container with overlaid text
|
||||
const thumbnailContainer = this.createThumbnailWithOverlay(video, isSwiperMode);
|
||||
const thumbnailContainer = this.createThumbnailWithOverlay(video, isSwiperMode, itemsPerView);
|
||||
item.appendChild(thumbnailContainer);
|
||||
|
||||
console.log('Created video item with overlay:', item);
|
||||
@ -331,7 +425,7 @@ class EndScreenOverlay extends Component {
|
||||
return container;
|
||||
}
|
||||
|
||||
createThumbnailWithOverlay(video, isSwiperMode = false) {
|
||||
createThumbnailWithOverlay(video, isSwiperMode = false, itemsPerView = 2) {
|
||||
const container = videojs.dom.createEl('div', {
|
||||
className: 'vjs-related-video-thumbnail-container',
|
||||
});
|
||||
@ -339,9 +433,13 @@ class EndScreenOverlay extends Component {
|
||||
// Container styling - full height since it contains everything
|
||||
container.style.position = 'relative';
|
||||
container.style.width = '100%';
|
||||
container.style.height = '120px';
|
||||
container.style.height = '100%';
|
||||
container.style.minHeight = '120px';
|
||||
container.style.overflow = 'hidden';
|
||||
container.style.borderRadius = '6px';
|
||||
container.style.borderRadius = '8px';
|
||||
container.style.flex = '1';
|
||||
container.style.display = 'flex';
|
||||
container.style.flexDirection = 'column';
|
||||
|
||||
// Create thumbnail image
|
||||
const thumbnail = videojs.dom.createEl('img', {
|
||||
@ -354,6 +452,9 @@ class EndScreenOverlay extends Component {
|
||||
thumbnail.style.height = '100%';
|
||||
thumbnail.style.objectFit = 'cover';
|
||||
thumbnail.style.display = 'block';
|
||||
thumbnail.style.flex = '1';
|
||||
thumbnail.style.minWidth = '0';
|
||||
thumbnail.style.minHeight = '0';
|
||||
|
||||
container.appendChild(thumbnail);
|
||||
|
||||
@ -370,7 +471,7 @@ class EndScreenOverlay extends Component {
|
||||
duration.style.color = 'white';
|
||||
duration.style.padding = '2px 6px';
|
||||
duration.style.borderRadius = '3px';
|
||||
duration.style.fontSize = '11px';
|
||||
duration.style.fontSize = itemsPerView === 4 ? '10px' : '11px';
|
||||
duration.style.fontWeight = '600';
|
||||
duration.style.lineHeight = '1';
|
||||
duration.style.zIndex = '3';
|
||||
@ -384,12 +485,12 @@ class EndScreenOverlay extends Component {
|
||||
});
|
||||
|
||||
textOverlay.style.position = 'absolute';
|
||||
textOverlay.style.top = '8px';
|
||||
textOverlay.style.left = '8px';
|
||||
textOverlay.style.right = '8px';
|
||||
textOverlay.style.top = itemsPerView === 4 ? '6px' : '8px';
|
||||
textOverlay.style.left = itemsPerView === 4 ? '6px' : '8px';
|
||||
textOverlay.style.right = itemsPerView === 4 ? '6px' : '8px';
|
||||
textOverlay.style.background =
|
||||
'linear-gradient(to bottom, rgba(0,0,0,0.8) 0%, rgba(0,0,0,0.4) 70%, transparent 100%)';
|
||||
textOverlay.style.padding = '8px';
|
||||
textOverlay.style.padding = itemsPerView === 4 ? '6px' : '8px';
|
||||
textOverlay.style.borderRadius = '4px';
|
||||
textOverlay.style.zIndex = '2';
|
||||
|
||||
@ -399,10 +500,11 @@ class EndScreenOverlay extends Component {
|
||||
});
|
||||
title.textContent = video.title || 'Sample Video Title';
|
||||
title.style.color = '#ffffff';
|
||||
title.style.fontSize = isSwiperMode ? '12px' : '13px';
|
||||
// Adjust font sizes based on items per view
|
||||
title.style.fontSize = itemsPerView === 4 ? '11px' : isSwiperMode ? '12px' : '13px';
|
||||
title.style.fontWeight = '600';
|
||||
title.style.lineHeight = '1.3';
|
||||
title.style.marginBottom = '4px';
|
||||
title.style.marginBottom = itemsPerView === 4 ? '3px' : '4px';
|
||||
title.style.overflow = 'hidden';
|
||||
title.style.textOverflow = 'ellipsis';
|
||||
title.style.display = '-webkit-box';
|
||||
@ -428,7 +530,8 @@ class EndScreenOverlay extends Component {
|
||||
|
||||
meta.textContent = metaText;
|
||||
meta.style.color = '#e0e0e0';
|
||||
meta.style.fontSize = isSwiperMode ? '10px' : '11px';
|
||||
// Adjust font sizes based on items per view
|
||||
meta.style.fontSize = itemsPerView === 4 ? '9px' : isSwiperMode ? '10px' : '11px';
|
||||
meta.style.lineHeight = '1.2';
|
||||
meta.style.overflow = 'hidden';
|
||||
meta.style.textOverflow = 'ellipsis';
|
||||
@ -529,7 +632,7 @@ class EndScreenOverlay extends Component {
|
||||
return info;
|
||||
}
|
||||
|
||||
createSwiperIndicators(totalVideos, swiperWrapper) {
|
||||
createSwiperIndicators(totalVideos, swiperWrapper, itemsPerView = 2) {
|
||||
const indicators = videojs.dom.createEl('div', {
|
||||
className: 'vjs-swiper-indicators',
|
||||
});
|
||||
@ -539,7 +642,6 @@ class EndScreenOverlay extends Component {
|
||||
indicators.style.gap = '8px';
|
||||
indicators.style.marginTop = '10px';
|
||||
|
||||
const itemsPerView = 2;
|
||||
const totalPages = Math.ceil(totalVideos / itemsPerView);
|
||||
|
||||
for (let i = 0; i < totalPages; i++) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user