Added picture-in-picture button to video player and search filter for manage content page
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user