mirror of
https://github.com/mediacms-io/mediacms.git
synced 2025-11-21 22:07:59 -05:00
Bulk actions support (#1418)
This commit is contained in:
@@ -20,6 +20,7 @@ class CustomChaptersOverlay extends Component {
|
||||
this.touchStartTime = 0;
|
||||
this.touchThreshold = 150; // ms for tap vs scroll detection
|
||||
this.isSmallScreen = window.innerWidth <= 480;
|
||||
this.scrollY = 0; // Track scroll position before locking
|
||||
|
||||
// Bind methods
|
||||
this.createOverlay = this.createOverlay.bind(this);
|
||||
@@ -31,6 +32,8 @@ class CustomChaptersOverlay extends Component {
|
||||
this.handleMobileInteraction = this.handleMobileInteraction.bind(this);
|
||||
this.setupResizeListener = this.setupResizeListener.bind(this);
|
||||
this.handleResize = this.handleResize.bind(this);
|
||||
this.lockBodyScroll = this.lockBodyScroll.bind(this);
|
||||
this.unlockBodyScroll = this.unlockBodyScroll.bind(this);
|
||||
|
||||
// Initialize after player is ready
|
||||
this.player().ready(() => {
|
||||
@@ -65,6 +68,9 @@ class CustomChaptersOverlay extends Component {
|
||||
|
||||
const el = this.player().el();
|
||||
if (el) el.classList.remove('chapters-open');
|
||||
|
||||
// Restore body scroll on mobile when closing
|
||||
this.unlockBodyScroll();
|
||||
}
|
||||
|
||||
setupResizeListener() {
|
||||
@@ -164,6 +170,8 @@ class CustomChaptersOverlay extends Component {
|
||||
this.overlay.style.display = 'none';
|
||||
const el = this.player().el();
|
||||
if (el) el.classList.remove('chapters-open');
|
||||
// Restore body scroll on mobile when closing
|
||||
this.unlockBodyScroll();
|
||||
};
|
||||
chapterClose.appendChild(closeBtn);
|
||||
playlistTitle.appendChild(chapterClose);
|
||||
@@ -355,6 +363,37 @@ class CustomChaptersOverlay extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
lockBodyScroll() {
|
||||
if (!this.isMobile) return;
|
||||
|
||||
// Save current scroll position
|
||||
this.scrollY = window.scrollY || window.pageYOffset;
|
||||
|
||||
// Lock body scroll with proper iOS handling
|
||||
document.body.style.overflow = 'hidden';
|
||||
document.body.style.position = 'fixed';
|
||||
document.body.style.top = `-${this.scrollY}px`;
|
||||
document.body.style.left = '0';
|
||||
document.body.style.right = '0';
|
||||
document.body.style.width = '100%';
|
||||
}
|
||||
|
||||
unlockBodyScroll() {
|
||||
if (!this.isMobile) return;
|
||||
|
||||
// Restore body scroll
|
||||
const scrollY = this.scrollY;
|
||||
document.body.style.overflow = '';
|
||||
document.body.style.position = '';
|
||||
document.body.style.top = '';
|
||||
document.body.style.left = '';
|
||||
document.body.style.right = '';
|
||||
document.body.style.width = '';
|
||||
|
||||
// Restore scroll position
|
||||
window.scrollTo(0, scrollY);
|
||||
}
|
||||
|
||||
toggleOverlay() {
|
||||
if (!this.overlay) return;
|
||||
|
||||
@@ -369,17 +408,11 @@ class CustomChaptersOverlay extends Component {
|
||||
navigator.vibrate(30);
|
||||
}
|
||||
|
||||
// Prevent body scroll on mobile when overlay is open
|
||||
if (this.isMobile) {
|
||||
if (isHidden) {
|
||||
document.body.style.overflow = 'hidden';
|
||||
document.body.style.position = 'fixed';
|
||||
document.body.style.width = '100%';
|
||||
} else {
|
||||
document.body.style.overflow = '';
|
||||
document.body.style.position = '';
|
||||
document.body.style.width = '';
|
||||
}
|
||||
// Lock/unlock body scroll on mobile when overlay opens/closes
|
||||
if (isHidden) {
|
||||
this.lockBodyScroll();
|
||||
} else {
|
||||
this.unlockBodyScroll();
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -390,7 +423,9 @@ class CustomChaptersOverlay extends Component {
|
||||
m.classList.remove('vjs-lock-showing');
|
||||
m.style.display = 'none';
|
||||
});
|
||||
} catch (e) {}
|
||||
} catch {
|
||||
// Ignore errors when closing menus
|
||||
}
|
||||
}
|
||||
|
||||
updateCurrentChapter() {
|
||||
@@ -406,7 +441,6 @@ class CustomChaptersOverlay extends Component {
|
||||
currentTime >= chapter.startTime &&
|
||||
(index === this.chaptersData.length - 1 || currentTime < this.chaptersData[index + 1].startTime);
|
||||
|
||||
const handle = item.querySelector('.playlist-drag-handle');
|
||||
const dynamic = item.querySelector('.meta-dynamic');
|
||||
if (isPlaying) {
|
||||
currentChapterIndex = index;
|
||||
@@ -463,11 +497,7 @@ class CustomChaptersOverlay extends Component {
|
||||
if (el) el.classList.remove('chapters-open');
|
||||
|
||||
// Restore body scroll on mobile
|
||||
if (this.isMobile) {
|
||||
document.body.style.overflow = '';
|
||||
document.body.style.position = '';
|
||||
document.body.style.width = '';
|
||||
}
|
||||
this.unlockBodyScroll();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -479,11 +509,7 @@ class CustomChaptersOverlay extends Component {
|
||||
if (el) el.classList.remove('chapters-open');
|
||||
|
||||
// Restore body scroll on mobile when disposing
|
||||
if (this.isMobile) {
|
||||
document.body.style.overflow = '';
|
||||
document.body.style.position = '';
|
||||
document.body.style.width = '';
|
||||
}
|
||||
this.unlockBodyScroll();
|
||||
|
||||
// Clean up event listeners
|
||||
if (this.handleResize) {
|
||||
|
||||
@@ -25,6 +25,7 @@ class CustomSettingsMenu extends Component {
|
||||
this.isMobile = this.detectMobile();
|
||||
this.isSmallScreen = window.innerWidth <= 480;
|
||||
this.touchThreshold = 150; // ms for tap vs scroll detection
|
||||
this.scrollY = 0; // Track scroll position before locking
|
||||
|
||||
// Bind methods
|
||||
this.createSettingsButton = this.createSettingsButton.bind(this);
|
||||
@@ -41,6 +42,8 @@ class CustomSettingsMenu extends Component {
|
||||
this.detectMobile = this.detectMobile.bind(this);
|
||||
this.handleMobileInteraction = this.handleMobileInteraction.bind(this);
|
||||
this.setupResizeListener = this.setupResizeListener.bind(this);
|
||||
this.lockBodyScroll = this.lockBodyScroll.bind(this);
|
||||
this.unlockBodyScroll = this.unlockBodyScroll.bind(this);
|
||||
|
||||
// Initialize after player is ready
|
||||
this.player().ready(() => {
|
||||
@@ -656,6 +659,8 @@ class CustomSettingsMenu extends Component {
|
||||
if (btnEl) {
|
||||
btnEl.classList.remove('settings-clicked');
|
||||
}
|
||||
// Restore body scroll on mobile when closing
|
||||
this.unlockBodyScroll();
|
||||
};
|
||||
|
||||
closeButton.addEventListener('click', closeFunction);
|
||||
@@ -942,6 +947,37 @@ class CustomSettingsMenu extends Component {
|
||||
this.startSubtitleSync();
|
||||
}
|
||||
|
||||
lockBodyScroll() {
|
||||
if (!this.isMobile) return;
|
||||
|
||||
// Save current scroll position
|
||||
this.scrollY = window.scrollY || window.pageYOffset;
|
||||
|
||||
// Lock body scroll with proper iOS handling
|
||||
document.body.style.overflow = 'hidden';
|
||||
document.body.style.position = 'fixed';
|
||||
document.body.style.top = `-${this.scrollY}px`;
|
||||
document.body.style.left = '0';
|
||||
document.body.style.right = '0';
|
||||
document.body.style.width = '100%';
|
||||
}
|
||||
|
||||
unlockBodyScroll() {
|
||||
if (!this.isMobile) return;
|
||||
|
||||
// Restore body scroll
|
||||
const scrollY = this.scrollY;
|
||||
document.body.style.overflow = '';
|
||||
document.body.style.position = '';
|
||||
document.body.style.top = '';
|
||||
document.body.style.left = '';
|
||||
document.body.style.right = '';
|
||||
document.body.style.width = '';
|
||||
|
||||
// Restore scroll position
|
||||
window.scrollTo(0, scrollY);
|
||||
}
|
||||
|
||||
toggleSettings(e) {
|
||||
// e.stopPropagation();
|
||||
const isVisible = this.settingsOverlay.classList.contains('show');
|
||||
@@ -954,11 +990,7 @@ class CustomSettingsMenu extends Component {
|
||||
this.stopKeepingControlsVisible();
|
||||
|
||||
// Restore body scroll on mobile when closing
|
||||
if (this.isMobile) {
|
||||
document.body.style.overflow = '';
|
||||
document.body.style.position = '';
|
||||
document.body.style.width = '';
|
||||
}
|
||||
this.unlockBodyScroll();
|
||||
} else {
|
||||
this.settingsOverlay.classList.add('show');
|
||||
this.settingsOverlay.style.display = 'block';
|
||||
@@ -972,11 +1004,7 @@ class CustomSettingsMenu extends Component {
|
||||
}
|
||||
|
||||
// Prevent body scroll on mobile when overlay is open
|
||||
if (this.isMobile) {
|
||||
document.body.style.overflow = 'hidden';
|
||||
document.body.style.position = 'fixed';
|
||||
document.body.style.width = '100%';
|
||||
}
|
||||
this.lockBodyScroll();
|
||||
}
|
||||
|
||||
this.speedSubmenu.style.display = 'none'; // Hide submenu when main menu toggles
|
||||
@@ -1002,6 +1030,9 @@ class CustomSettingsMenu extends Component {
|
||||
this.settingsOverlay.classList.add('show');
|
||||
this.settingsOverlay.style.display = 'block';
|
||||
|
||||
// Lock body scroll when opening
|
||||
this.lockBodyScroll();
|
||||
|
||||
// Hide other submenus and show subtitles submenu
|
||||
this.speedSubmenu.style.display = 'none';
|
||||
if (this.qualitySubmenu) this.qualitySubmenu.style.display = 'none';
|
||||
@@ -1072,11 +1103,7 @@ class CustomSettingsMenu extends Component {
|
||||
}
|
||||
|
||||
// Restore body scroll on mobile when closing
|
||||
if (this.isMobile) {
|
||||
document.body.style.overflow = '';
|
||||
document.body.style.position = '';
|
||||
document.body.style.width = '';
|
||||
}
|
||||
this.unlockBodyScroll();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1417,6 +1444,8 @@ class CustomSettingsMenu extends Component {
|
||||
if (btnEl) {
|
||||
btnEl.classList.remove('settings-clicked');
|
||||
}
|
||||
// Restore body scroll on mobile when closing
|
||||
this.unlockBodyScroll();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1493,11 +1522,7 @@ class CustomSettingsMenu extends Component {
|
||||
}
|
||||
|
||||
// Restore body scroll on mobile when disposing
|
||||
if (this.isMobile) {
|
||||
document.body.style.overflow = '';
|
||||
document.body.style.position = '';
|
||||
document.body.style.width = '';
|
||||
}
|
||||
this.unlockBodyScroll();
|
||||
|
||||
// Remove DOM elements
|
||||
if (this.settingsOverlay) {
|
||||
|
||||
@@ -49,10 +49,7 @@ class EndScreenOverlay extends Component {
|
||||
|
||||
// Get videos to show - access directly from options during createEl
|
||||
const relatedVideos = this.options_?.relatedVideos || this.relatedVideos || [];
|
||||
const videosToShow =
|
||||
relatedVideos.length > 0
|
||||
? relatedVideos.slice(0, maxVideos)
|
||||
: this.createSampleVideos().slice(0, maxVideos);
|
||||
const videosToShow = relatedVideos.slice(0, maxVideos);
|
||||
|
||||
if (useSwiper) {
|
||||
return this.createSwiperGrid(videosToShow, itemsPerView || 2, columns, gridRows || 1);
|
||||
@@ -307,8 +304,8 @@ class EndScreenOverlay extends Component {
|
||||
if (this.relatedVideos && Array.isArray(this.relatedVideos) && this.relatedVideos.length > 0) {
|
||||
return this.relatedVideos.slice(0, maxVideos);
|
||||
}
|
||||
// Fallback to sample videos for testing
|
||||
return this.createSampleVideos().slice(0, maxVideos);
|
||||
// Return empty array if no related videos
|
||||
return [];
|
||||
}
|
||||
|
||||
createVideoItem(video, isSwiperMode = false, itemsPerView = 2, isGridMode = false) {
|
||||
@@ -745,153 +742,12 @@ class EndScreenOverlay extends Component {
|
||||
return 'ontouchstart' in window || navigator.maxTouchPoints > 0;
|
||||
}
|
||||
|
||||
createSampleVideos() {
|
||||
return [
|
||||
{
|
||||
id: 'sample1',
|
||||
title: 'React Full Course - Complete Tutorial for Beginners',
|
||||
author: 'Bro Code',
|
||||
views: '2.1M views',
|
||||
duration: 1800,
|
||||
},
|
||||
{
|
||||
id: 'sample2',
|
||||
title: 'JavaScript ES6+ Modern Features',
|
||||
author: 'Tech Tutorials',
|
||||
views: '850K views',
|
||||
duration: 1200,
|
||||
},
|
||||
{
|
||||
id: 'sample3',
|
||||
title: 'CSS Grid Layout Masterclass',
|
||||
author: 'Web Dev Academy',
|
||||
views: '1.2M views',
|
||||
duration: 2400,
|
||||
},
|
||||
{
|
||||
id: 'sample4',
|
||||
title: 'Node.js Backend Development',
|
||||
author: 'Code Master',
|
||||
views: '650K views',
|
||||
duration: 3600,
|
||||
},
|
||||
{
|
||||
id: 'sample5',
|
||||
title: 'Vue.js Complete Guide',
|
||||
author: 'Frontend Pro',
|
||||
views: '980K views',
|
||||
duration: 2800,
|
||||
},
|
||||
{
|
||||
id: 'sample6',
|
||||
title: 'Python Data Science Bootcamp',
|
||||
author: 'Data Academy',
|
||||
views: '1.5M views',
|
||||
duration: 4200,
|
||||
},
|
||||
{
|
||||
id: 'sample7',
|
||||
title: 'TypeScript for Beginners',
|
||||
author: 'Code School',
|
||||
views: '750K views',
|
||||
duration: 1950,
|
||||
},
|
||||
{
|
||||
id: 'sample8',
|
||||
title: 'Docker Container Tutorial',
|
||||
author: 'DevOps Pro',
|
||||
views: '920K views',
|
||||
duration: 2700,
|
||||
},
|
||||
{
|
||||
id: 'sample9',
|
||||
title: 'MongoDB Database Design',
|
||||
author: 'DB Expert',
|
||||
views: '580K views',
|
||||
duration: 3200,
|
||||
},
|
||||
{
|
||||
id: 'sample10',
|
||||
title: 'AWS Cloud Computing Essentials',
|
||||
author: 'Cloud Master',
|
||||
views: '1.8M views',
|
||||
duration: 4800,
|
||||
},
|
||||
{
|
||||
id: 'sample11',
|
||||
title: 'GraphQL API Development',
|
||||
author: 'API Guru',
|
||||
views: '420K views',
|
||||
duration: 2100,
|
||||
},
|
||||
{
|
||||
id: 'sample12',
|
||||
title: 'Kubernetes Orchestration Guide',
|
||||
author: 'Container Pro',
|
||||
views: '680K views',
|
||||
duration: 3900,
|
||||
},
|
||||
{
|
||||
id: 'sample13',
|
||||
title: 'Redis Caching Strategies',
|
||||
author: 'Cache Expert',
|
||||
views: '520K views',
|
||||
duration: 2250,
|
||||
},
|
||||
{
|
||||
id: 'sample14',
|
||||
title: 'Web Performance Optimization',
|
||||
author: 'Speed Master',
|
||||
views: '890K views',
|
||||
duration: 3100,
|
||||
},
|
||||
{
|
||||
id: 'sample15',
|
||||
title: 'CI/CD Pipeline Setup',
|
||||
author: 'DevOps Guide',
|
||||
views: '710K views',
|
||||
duration: 2900,
|
||||
},
|
||||
{
|
||||
id: 'sample16',
|
||||
title: 'Microservices Architecture',
|
||||
author: 'System Design',
|
||||
views: '1.3M views',
|
||||
duration: 4500,
|
||||
},
|
||||
{
|
||||
id: 'sample17',
|
||||
title: 'Next.js App Router Tutorial',
|
||||
author: 'Web Academy',
|
||||
views: '640K views',
|
||||
duration: 2650,
|
||||
},
|
||||
{
|
||||
id: 'sample18',
|
||||
title: 'Tailwind CSS Crash Course',
|
||||
author: 'CSS Master',
|
||||
views: '1.1M views',
|
||||
duration: 1800,
|
||||
},
|
||||
{
|
||||
id: 'sample19',
|
||||
title: 'Git and GitHub Essentials',
|
||||
author: 'Version Control Pro',
|
||||
views: '2.3M views',
|
||||
duration: 3300,
|
||||
},
|
||||
{
|
||||
id: 'sample20',
|
||||
title: 'REST API Best Practices',
|
||||
author: 'API Design',
|
||||
views: '780K views',
|
||||
duration: 2400,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
show() {
|
||||
this.el().style.display = 'flex';
|
||||
// Only show if there are related videos
|
||||
const relatedVideos = this.options_?.relatedVideos || this.relatedVideos || [];
|
||||
if (relatedVideos.length > 0) {
|
||||
this.el().style.display = 'flex';
|
||||
}
|
||||
}
|
||||
|
||||
hide() {
|
||||
|
||||
@@ -139,12 +139,6 @@ video::cue {
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.video-js .vjs-control-bar .vjs-autoplay-toggle {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 500px) {
|
||||
.video-js .vjs-control-bar .vjs-next-video-button {
|
||||
display: none !important;
|
||||
|
||||
Reference in New Issue
Block a user