Added picture-in-picture button to video player and search filter for manage content page
This commit is contained in:
@@ -189,6 +189,12 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button class="watch-btn" id="btn-pip" title="Picture-in-Picture">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="icon">
|
||||
<path
|
||||
d="M19 7h-8v6h8V7zm2-4H3c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h18c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H3V5h18v14z" />
|
||||
</svg>
|
||||
</button>
|
||||
<button class="watch-btn" id="btn-fullscreen" title="Fullscreen">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="icon">
|
||||
<path d="M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z" />
|
||||
@@ -517,6 +523,10 @@
|
||||
<select id="content-source-select" class="source-select">
|
||||
<option value="">Select a source...</option>
|
||||
</select>
|
||||
<div class="search-wrapper">
|
||||
<input type="text" id="content-search" placeholder="Search groups/channels..." class="search-input">
|
||||
<button type="button" class="search-clear" title="Clear search">×</button>
|
||||
</div>
|
||||
<div class="content-actions">
|
||||
<button class="btn btn-sm btn-secondary" id="content-show-all">Show All</button>
|
||||
<button class="btn btn-sm btn-secondary" id="content-hide-all">Hide All</button>
|
||||
@@ -674,6 +684,12 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button class="watch-btn" id="watch-pip" title="Picture-in-Picture">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="icon">
|
||||
<path
|
||||
d="M19 7h-8v6h8V7zm2-4H3c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h18c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H3V5h18v14z" />
|
||||
</svg>
|
||||
</button>
|
||||
<button class="watch-btn" id="watch-fullscreen" title="Fullscreen">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="icon">
|
||||
<path d="M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z" />
|
||||
|
||||
@@ -15,6 +15,7 @@ class SourceManager {
|
||||
this.hiddenSet = new Set(); // Set of hidden item keys (current state)
|
||||
this.originalHiddenSet = new Set(); // Set of hidden item keys (state when loaded)
|
||||
this.expandedGroups = new Set(); // Set of expanded group IDs
|
||||
this.searchQuery = ''; // Search filter for content browser
|
||||
|
||||
this.init();
|
||||
}
|
||||
@@ -426,6 +427,23 @@ class SourceManager {
|
||||
|
||||
// Save Changes button
|
||||
document.getElementById('content-save')?.addEventListener('click', () => this.saveContentChanges());
|
||||
|
||||
// Search input
|
||||
const searchInput = document.getElementById('content-search');
|
||||
const searchClear = searchInput?.parentElement?.querySelector('.search-clear');
|
||||
|
||||
searchInput?.addEventListener('input', (e) => {
|
||||
this.searchQuery = e.target.value.toLowerCase().trim();
|
||||
this.renderTree();
|
||||
});
|
||||
|
||||
searchClear?.addEventListener('click', () => {
|
||||
if (searchInput) {
|
||||
searchInput.value = '';
|
||||
this.searchQuery = '';
|
||||
this.renderTree();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -565,16 +583,45 @@ class SourceManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get groups filtered by search query
|
||||
*/
|
||||
getFilteredGroups() {
|
||||
if (!this.treeData?.groups) return [];
|
||||
if (!this.searchQuery) return this.treeData.groups;
|
||||
|
||||
return this.treeData.groups
|
||||
.map(group => {
|
||||
// Check if group name matches
|
||||
const groupMatches = group.name.toLowerCase().includes(this.searchQuery);
|
||||
|
||||
// Filter items that match
|
||||
const matchingItems = group.items.filter(item =>
|
||||
item.name.toLowerCase().includes(this.searchQuery)
|
||||
);
|
||||
|
||||
// Include group if name matches OR has matching items
|
||||
if (groupMatches || matchingItems.length > 0) {
|
||||
return { ...group, items: groupMatches ? group.items : matchingItems };
|
||||
}
|
||||
return null;
|
||||
})
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the full tree based on current state
|
||||
*/
|
||||
renderTree() {
|
||||
if (!this.treeData || !this.treeData.groups.length) {
|
||||
this.contentTree.innerHTML = '<p class="hint">No content found</p>';
|
||||
const groups = this.getFilteredGroups();
|
||||
|
||||
if (!groups.length) {
|
||||
const msg = this.searchQuery ? 'No matches found' : 'No content found';
|
||||
this.contentTree.innerHTML = `<p class="hint">${msg}</p>`;
|
||||
return;
|
||||
}
|
||||
|
||||
const html = this.treeData.groups.map(group => this.getGroupHtml(group)).join('');
|
||||
const html = groups.map(group => this.getGroupHtml(group)).join('');
|
||||
this.contentTree.innerHTML = html;
|
||||
|
||||
// Attach event listeners
|
||||
@@ -669,10 +716,11 @@ class SourceManager {
|
||||
this.expandedGroups.add(groupId);
|
||||
}
|
||||
|
||||
// Re-render only this group
|
||||
// Re-render only this group - use filtered groups to respect search
|
||||
const groupEl = this.contentTree.querySelector(`.content-group[data-group-id="${CSS.escape(groupId)}"]`);
|
||||
if (groupEl) {
|
||||
const group = this.treeData.groups.find(g => g.id === groupId);
|
||||
const filteredGroups = this.getFilteredGroups();
|
||||
const group = filteredGroups.find(g => g.id === groupId);
|
||||
if (group) {
|
||||
const newHtml = this.getGroupHtml(group);
|
||||
groupEl.outerHTML = newHtml;
|
||||
|
||||
@@ -267,6 +267,13 @@ class VideoPlayer {
|
||||
this.toggleFullscreen();
|
||||
});
|
||||
|
||||
// Picture-in-Picture
|
||||
const btnPip = document.getElementById('btn-pip');
|
||||
btnPip?.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
this.togglePictureInPicture();
|
||||
});
|
||||
|
||||
this.container.addEventListener('dblclick', () => this.toggleFullscreen());
|
||||
|
||||
// Overlay Auto-hide Logic
|
||||
@@ -325,6 +332,24 @@ class VideoPlayer {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle Picture-in-Picture mode
|
||||
*/
|
||||
async togglePictureInPicture() {
|
||||
try {
|
||||
if (document.pictureInPictureElement) {
|
||||
await document.exitPictureInPicture();
|
||||
} else if (document.pictureInPictureEnabled && this.video.readyState >= 2) {
|
||||
await this.video.requestPictureInPicture();
|
||||
}
|
||||
} catch (err) {
|
||||
// Silently fail - Firefox users can use native PiP button
|
||||
if (err.name !== 'NotAllowedError') {
|
||||
console.error('Picture-in-Picture error:', err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
|
||||
@@ -102,6 +102,10 @@ class WatchPage {
|
||||
// Fullscreen
|
||||
this.fullscreenBtn?.addEventListener('click', () => this.toggleFullscreen());
|
||||
|
||||
// Picture-in-Picture
|
||||
const pipBtn = document.getElementById('watch-pip');
|
||||
pipBtn?.addEventListener('click', () => this.togglePictureInPicture());
|
||||
|
||||
// Progress bar
|
||||
this.progressSlider?.addEventListener('input', (e) => this.seek(e.target.value));
|
||||
|
||||
@@ -374,6 +378,21 @@ class WatchPage {
|
||||
}
|
||||
}
|
||||
|
||||
async togglePictureInPicture() {
|
||||
try {
|
||||
if (document.pictureInPictureElement) {
|
||||
await document.exitPictureInPicture();
|
||||
} else if (document.pictureInPictureEnabled && this.video.readyState >= 2) {
|
||||
await this.video.requestPictureInPicture();
|
||||
}
|
||||
} catch (err) {
|
||||
// Silently fail - Firefox users can use native PiP button
|
||||
if (err.name !== 'NotAllowedError') {
|
||||
console.error('Picture-in-Picture error:', err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// === UI Updates ===
|
||||
|
||||
updateProgress() {
|
||||
|
||||
Reference in New Issue
Block a user