mirror of
https://github.com/ranaldsgift/KefinTweaks.git
synced 2026-11-05 07:42:47 +01:00
Move scripts to /scripts folder
This commit is contained in:
@@ -117,9 +117,11 @@ The features in KefinTweaks which use local data caching are listed below, along
|
||||
- ✅ [Watched History](https://features.jellyfin.org/posts/633/watched-history)
|
||||
- ✅ [On-Demand Subtitle Search in Video OSD](https://features.jellyfin.org/posts/3385/on-demand-subtitle-search)
|
||||
- ✅ [Add genres and recommendations to home screen](https://features.jellyfin.org/posts/3501/add-genres-and-recommendations-to-home-screen)
|
||||
- ✅ [New or Improved Home Screen Sections](https://features.jellyfin.org/posts/1986/new-or-improved-home-secreen-sections)
|
||||
- ✅ [Custom Carousel Options on Home Screen](https://features.jellyfin.org/posts/1439/custom-carousel-options-on-home-screen)
|
||||
- ✅ [Improved Playlist UX](https://features.jellyfin.org/posts/2823/playlist-moviesummary)
|
||||
- ✅ [Flattening TV Shows with 1 season](https://features.jellyfin.org/posts/8/flattening-tv-shows) [[2]](https://features.jellyfin.org/posts/3352/add-option-to-flatten-single-season-tv-shows-skip-season-level)
|
||||
- ✅ [List all collections that an item belongs to on the details page](https://features.jellyfin.org/posts/540/list-all-collections-that-a-movie-belong-to-in-movie-details) [[2]](https://features.jellyfin.org/posts/3540/collection-data-present-on-content-within-the-collection)
|
||||
- ✅ [List all collections that an item belongs to on the details page](https://features.jellyfin.org/posts/540/list-all-collections-that-a-movie-belong-to-in-movie-details) [[2]](https://features.jellyfin.org/posts/3540/collection-data-present-on-content-within-the-collection) [[3]](https://features.jellyfin.org/posts/3549/show-collections-on-movie-page)
|
||||
- ✅ [Custom Skins/Themes](https://features.jellyfin.org/posts/2509/themes-skins-or-clone-plex-layout-to-convert-the-rest-of-the-plex-and-emby-user-base-to-jellyfin) [[2]](https://features.jellyfin.org/posts/2616/theme-skins)
|
||||
- ✅ [TV Season Selection at the Series level](https://forum.jellyfin.org/t-tv-season-selection-when-browsing-seasons)
|
||||
|
||||
@@ -183,6 +185,9 @@ Creates sections for movies and episodes released in the last 7 days (trending s
|
||||
- **Infinite Discovery Sections**:
|
||||
Load discovery sections based on items you've watched and favorited, as well as from genres or top people in your library
|
||||
|
||||
- **HSS Support**:
|
||||
Full compatibility with [Home Screen Sections](https://github.com/IAmParadox27/jellyfin-plugin-home-sections) plugin by [IAmParadox27](https://github.com/IAmParadox27/)
|
||||
|
||||
- **Seasonal Content**:
|
||||
Seasonally-themed sections (Halloween, Christmas, etc.)
|
||||
|
||||
@@ -269,6 +274,9 @@ Adds a card overlay button to remove items from Continue Watching for all resuma
|
||||
</div>
|
||||
<br/>
|
||||
|
||||
#### **Collection Sorting**:
|
||||
Adds the ability to sort collection items on the Collection page. Supports sorting by Title, Release Date, Date Added, Community Rating, Critic Rating.
|
||||
|
||||
### Navigation Improvements
|
||||
|
||||
#### **Header Tab Enhancements**:
|
||||
@@ -413,6 +421,7 @@ Allow users to also customize various configuration options through the UI
|
||||
<hr>
|
||||
|
||||
### Version History
|
||||
- **v0.2.3**: New Feature: Collection Sorting, general fixes
|
||||
- **v0.2.2**: Add support for skin server versions, fixes for HeaderTabs
|
||||
- **v0.2.1**: Caching improvements for Watchlist and Top People, Mobile UI fixes and Flatten TV Shows improvements
|
||||
- **v0.2.0**: New Features: Skin Manager, Collections on Details Page, Flatten TV Shows
|
||||
|
||||
@@ -1,99 +0,0 @@
|
||||
// Custom Menu Script
|
||||
// Removes target="_blank" from custom menu links to prevent opening in new tabs
|
||||
// Ensures all custom menu links open in the same tab for better user experience
|
||||
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
// Common logging function
|
||||
const LOG = (...args) => console.log('[CustomMenu]', ...args);
|
||||
const WARN = (...args) => console.warn('[CustomMenu]', ...args);
|
||||
const ERR = (...args) => console.error('[CustomMenu]', ...args);
|
||||
|
||||
LOG('Script loaded - JS Injector mode');
|
||||
|
||||
function initMenuWatcher() {
|
||||
const menuContainer = document.querySelector('.customMenuOptions');
|
||||
if (!menuContainer) return false;
|
||||
|
||||
LOG('Custom menu container found, initializing watcher');
|
||||
|
||||
const cleanLinks = (node) => {
|
||||
if (node.matches?.('a.navMenuOption[target="_blank"]')) {
|
||||
LOG('Removing target="_blank" from menu link');
|
||||
node.removeAttribute('target');
|
||||
}
|
||||
node.querySelectorAll?.('a.navMenuOption[target="_blank"]').forEach(link => {
|
||||
LOG('Removing target="_blank" from nested menu link');
|
||||
link.removeAttribute('target');
|
||||
});
|
||||
};
|
||||
|
||||
// Clean existing links
|
||||
cleanLinks(menuContainer);
|
||||
|
||||
// Watch for new/changed nodes
|
||||
const observer = new MutationObserver((mutations) => {
|
||||
for (const mutation of mutations) {
|
||||
mutation.addedNodes.forEach(node => cleanLinks(node));
|
||||
if (mutation.type === 'attributes' && mutation.target.matches('a.navMenuOption')) {
|
||||
cleanLinks(mutation.target);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
observer.observe(menuContainer, {
|
||||
childList: true,
|
||||
subtree: true,
|
||||
attributes: true,
|
||||
attributeFilter: ['target']
|
||||
});
|
||||
|
||||
LOG('Menu watcher initialized successfully');
|
||||
return true;
|
||||
}
|
||||
|
||||
// Watch for menu container to appear
|
||||
const waitForMenu = () => {
|
||||
const menuContainer = document.querySelector('.customMenuOptions');
|
||||
if (menuContainer) {
|
||||
initMenuWatcher();
|
||||
return;
|
||||
}
|
||||
|
||||
LOG('Custom menu container not found, waiting for it to appear');
|
||||
|
||||
// Use MutationObserver to watch for the menu container
|
||||
const observer = new MutationObserver((mutations) => {
|
||||
for (const mutation of mutations) {
|
||||
mutation.addedNodes.forEach(node => {
|
||||
if (node.nodeType === Node.ELEMENT_NODE) {
|
||||
// Check if the added node is the menu or contains it
|
||||
if (node.matches?.('.customMenuOptions') || node.querySelector?.('.customMenuOptions')) {
|
||||
LOG('Custom menu container detected, initializing watcher');
|
||||
observer.disconnect();
|
||||
initMenuWatcher();
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
observer.observe(document.body, {
|
||||
childList: true,
|
||||
subtree: true
|
||||
});
|
||||
|
||||
// Fallback timeout after 10 seconds
|
||||
setTimeout(() => {
|
||||
observer.disconnect();
|
||||
WARN('Menu not found after 10 seconds');
|
||||
}, 10000);
|
||||
};
|
||||
|
||||
// Start watching
|
||||
waitForMenu();
|
||||
|
||||
LOG('Custom menu functionality initialized');
|
||||
})();
|
||||
+10
-3
@@ -6,7 +6,7 @@
|
||||
'use strict';
|
||||
|
||||
console.log('[KefinTweaks Injector] Initializing...');
|
||||
const versionNumber = "0.2.2";
|
||||
const versionNumber = "0.2.3";
|
||||
|
||||
// Configuration: Start with defaults, then merge user config
|
||||
// This allows new scripts to work out of the box without requiring config updates
|
||||
@@ -310,8 +310,15 @@
|
||||
}
|
||||
|
||||
// Get the root path for scripts from configuration
|
||||
function getScriptRoot() {
|
||||
return window.KefinTweaksConfig?.scriptRoot || 'https://ranaldsgift.github.io/KefinTweaks/';
|
||||
function getScriptRoot() {
|
||||
const configScriptRoot = window.KefinTweaksConfig?.scriptRoot;
|
||||
|
||||
// Handle legacy configuration pointing to the root directory
|
||||
if (configScriptRoot === 'https://ranaldsgift.github.io/KefinTweaks/') {
|
||||
return 'https://ranaldsgift.github.io/KefinTweaks/scripts/';
|
||||
}
|
||||
|
||||
return window.KefinTweaksConfig?.scriptRoot || 'https://ranaldsgift.github.io/KefinTweaks/scripts/';
|
||||
}
|
||||
|
||||
// Load a CSS file
|
||||
|
||||
+10
-7
@@ -33,10 +33,12 @@
|
||||
// - Search: Enable Jellyseerr integration adds an option to search for Jellyseerr results only
|
||||
//
|
||||
// Considerations for the home screen sections:
|
||||
// 1. Custom sections (order 1-29) - Your playlist-based sections
|
||||
// 2. New and Trending sections (order 30-49) - Newest movies and episodes
|
||||
// 3. Seasonal sections (order 50+) - Holiday-themed content
|
||||
// 4. Discovery sections (order 200+) - Intelligent recommendations based on your library
|
||||
// - Default Jellyfin sections (order 1-10)
|
||||
// - New releases sections (order 30-49) - Newest movies and episodes
|
||||
// - Seasonal sections (order 50-59) - Holiday-themed content
|
||||
// - One-off sections: Watchlist, TV Networks, section (order 60-69) - Your watchlist
|
||||
// - Discovery sections (order 1000+) - Intelligent recommendations based on your library
|
||||
// - Custom sections can be placed anywhere in the order by using the order field in the configuration
|
||||
|
||||
(function() {
|
||||
'use strict';
|
||||
@@ -50,7 +52,8 @@
|
||||
// SCRIPT ROOT CONFIGURATION
|
||||
// ============================================================================
|
||||
// Configure where scripts are loaded from
|
||||
scriptRoot: 'https://ranaldsgift.github.io/KefinTweaks/',
|
||||
kefinTweaksRoot: 'https://ranaldsgift.github.io/KefinTweaks/',
|
||||
scriptRoot: 'https://ranaldsgift.github.io/KefinTweaks/scripts/',
|
||||
|
||||
// ============================================================================
|
||||
// SCRIPT ENABLE/DISABLE FLAGS
|
||||
@@ -117,7 +120,7 @@
|
||||
enableDiscovery: true, // Enables discovery sections on the home screen
|
||||
|
||||
// Discovery section behavior and filtering
|
||||
enableInfiniteScroll: false, // Use infinite scroll vs Load More button
|
||||
enableInfiniteScroll: true, // Use infinite scroll vs Load More button
|
||||
minPeopleAppearances: 10, // Minimum movie appearances for people to be featured
|
||||
minGenreMovieCount: 50, // Minimum movie count for genres to be included
|
||||
minimumShowsForNetwork: 5, // Minimum show count for TV networks to be featured
|
||||
@@ -297,7 +300,7 @@
|
||||
// ============================================================================
|
||||
// Load the injector script which handles loading individual scripts based on configuration
|
||||
const script = document.createElement("script");
|
||||
script.src = `${KEFIN_TWEAKS_CONFIG.scriptRoot}injector.js?v=${new Date().getTime()}`;
|
||||
script.src = `${KEFIN_TWEAKS_CONFIG.kefinTweaksRoot}injector.js?v=${new Date().getTime()}`;
|
||||
script.async = true;
|
||||
document.head.appendChild(script);
|
||||
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
const KEFIN_TWEAKS_CONFIG = {
|
||||
kefinTweaksRoot: 'https://ranaldsgift.github.io/KefinTweaks/',
|
||||
scriptRoot: 'https://ranaldsgift.github.io/KefinTweaks/scripts/',
|
||||
|
||||
scripts: {
|
||||
collections: true,
|
||||
homeScreen: true,
|
||||
search: true,
|
||||
watchlist: true,
|
||||
headerTabs: true,
|
||||
customMenuLinks: true,
|
||||
breadcrumbs: true,
|
||||
playlist: true,
|
||||
itemDetailsCollections: true,
|
||||
flattenSingleSeasonShows: true,
|
||||
exclusiveElsewhere: false,
|
||||
backdropLeakFix: true,
|
||||
dashboardButtonFix: true,
|
||||
infiniteScroll: true,
|
||||
removeContinue: true,
|
||||
subtitleSearch: true,
|
||||
skinManager: true
|
||||
},
|
||||
homeScreen: {
|
||||
enableNewAndTrending: true,
|
||||
enableNewMovies: true,
|
||||
enableNewEpisodes: true,
|
||||
enableTrending: false,
|
||||
enableDiscovery: true,
|
||||
enableInfiniteScroll: true,
|
||||
minPeopleAppearances: 10,
|
||||
minGenreMovieCount: 50,
|
||||
minimumShowsForNetwork: 5,
|
||||
enableWatchlist: true,
|
||||
enableSeasonal: true,
|
||||
seasonalItemLimit: 16,
|
||||
customSections: [],
|
||||
},
|
||||
exclusiveElsewhere: {
|
||||
hideServerName: false
|
||||
},
|
||||
search: {
|
||||
enableJellyseerr: false
|
||||
},
|
||||
skins: [],
|
||||
defaultSkin: null,
|
||||
themes: [],
|
||||
customMenuLinks: [],
|
||||
};
|
||||
|
||||
window.KefinTweaksConfig = KEFIN_TWEAKS_CONFIG;
|
||||
|
||||
const script = document.createElement("script");
|
||||
script.src = `${KEFIN_TWEAKS_CONFIG.kefinTweaksRoot}injector.js?v=${new Date().getTime()}`;
|
||||
script.async = true;
|
||||
document.head.appendChild(script);
|
||||
|
||||
console.log('[KefinTweaks] Central configuration loaded. Available at window.KefinTweaksConfig');
|
||||
})();
|
||||
|
||||
@@ -547,7 +547,7 @@
|
||||
// Poll for section title (up to 5 seconds, every 500ms)
|
||||
const sectionTitle = await pollForElement(() => {
|
||||
return collectionItemsContainer.querySelector('.sectionTitle');
|
||||
}, 5000, 500);
|
||||
}, 5000, 100);
|
||||
|
||||
if (!sectionTitle) {
|
||||
WARN('Section title not found after polling for 5 seconds');
|
||||
@@ -31,4 +31,8 @@ div#seriesScheduleSection {
|
||||
.headerRight .headerAppearanceButton {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.itemBackdrop {
|
||||
pointer-events: none;
|
||||
}
|
||||
@@ -0,0 +1,392 @@
|
||||
// Device Manager
|
||||
// Adds a "Remove Device" button to active device sessions on the dashboard page
|
||||
// Allows users to remove devices via a custom endpoint
|
||||
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
// Logging functions
|
||||
const LOG = (...args) => console.log('[DeviceManager]', ...args);
|
||||
const WARN = (...args) => console.warn('[DeviceManager]', ...args);
|
||||
const ERR = (...args) => console.error('[DeviceManager]', ...args);
|
||||
|
||||
LOG('Initializing Device Manager');
|
||||
|
||||
// Polling interval references
|
||||
let pollingInterval = null;
|
||||
let continuousPollingInterval = null;
|
||||
|
||||
// Wait for KefinTweaksUtils to become available
|
||||
async function waitForUtils(maxWaitTime = 30000, checkInterval = 100) {
|
||||
const startTime = Date.now();
|
||||
|
||||
while (Date.now() - startTime < maxWaitTime) {
|
||||
if (window.KefinTweaksUtils && window.KefinTweaksUtils.onViewPage) {
|
||||
LOG('KefinTweaksUtils is now available');
|
||||
return true;
|
||||
}
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, checkInterval));
|
||||
}
|
||||
|
||||
ERR('KefinTweaksUtils not available after waiting. Device Manager requires utils.js');
|
||||
return false;
|
||||
}
|
||||
|
||||
// Initialize the device manager after utils are available
|
||||
async function initializeDeviceManager() {
|
||||
// Wait for utils to be available
|
||||
const utilsAvailable = await waitForUtils();
|
||||
if (!utilsAvailable) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Register handler for dashboard page
|
||||
window.KefinTweaksUtils.onViewPage((view, element) => {
|
||||
// Check if we're on the dashboard page
|
||||
const isDashboardPage = view === 'dashboard' ||
|
||||
view === 'dashboardPage' ||
|
||||
window.location.hash.includes('dashboard') ||
|
||||
document.querySelector('#dashboardPage:not(.hide)');
|
||||
|
||||
if (!isDashboardPage) {
|
||||
// Not on dashboard, clear polling intervals
|
||||
if (pollingInterval) {
|
||||
clearInterval(pollingInterval);
|
||||
pollingInterval = null;
|
||||
}
|
||||
if (continuousPollingInterval) {
|
||||
clearInterval(continuousPollingInterval);
|
||||
continuousPollingInterval = null;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
LOG('Dashboard page detected, initializing device manager');
|
||||
|
||||
// Start polling for activeDevicesContainer
|
||||
initializeDashboard();
|
||||
}, {
|
||||
pages: ['dashboard', 'dashboardPage']
|
||||
});
|
||||
|
||||
LOG('Device Manager initialized');
|
||||
}
|
||||
|
||||
// Check if ApiClient is available
|
||||
function isApiClientAvailable() {
|
||||
return window.ApiClient &&
|
||||
typeof window.ApiClient.accessToken === 'function' &&
|
||||
typeof window.ApiClient.serverAddress === 'function' &&
|
||||
typeof window.ApiClient.getSessions === 'function';
|
||||
}
|
||||
|
||||
// Get session data by session ID
|
||||
async function getSessionData(sessionId, sessions) {
|
||||
try {
|
||||
if (!isApiClientAvailable()) {
|
||||
throw new Error('ApiClient not available or getSessions method not found');
|
||||
}
|
||||
|
||||
if (!sessions || !Array.isArray(sessions)) {
|
||||
throw new Error('Failed to get sessions or sessions is not an array');
|
||||
}
|
||||
|
||||
LOG(`Retrieved ${sessions.length} sessions, filtering for session ID: ${sessionId}`);
|
||||
|
||||
// Filter sessions to find the one matching our session ID
|
||||
const sessionData = sessions.find(session => session.Id === sessionId);
|
||||
|
||||
if (!sessionData) {
|
||||
WARN(`Session with ID ${sessionId} not found in sessions list`);
|
||||
return null;
|
||||
}
|
||||
|
||||
return sessionData;
|
||||
} catch (error) {
|
||||
ERR('Error fetching session data:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Remove device via custom endpoint
|
||||
async function removeDevice(deviceId) {
|
||||
try {
|
||||
if (!isApiClientAvailable()) {
|
||||
throw new Error('ApiClient not available');
|
||||
}
|
||||
|
||||
const token = window.ApiClient.accessToken();
|
||||
if (!token) {
|
||||
throw new Error('Access token not available');
|
||||
}
|
||||
|
||||
LOG(`Removing device: ${deviceId}`);
|
||||
|
||||
const serverAddress = window.ApiClient.serverAddress();
|
||||
if (!serverAddress) {
|
||||
throw new Error('Server address not available');
|
||||
}
|
||||
|
||||
const response = await fetch(`${serverAddress}/Devices?Id=${encodeURIComponent(deviceId)}`, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'Authorization': `MediaBrowser Token="${token}"`
|
||||
}
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
||||
}
|
||||
|
||||
LOG('Device removed successfully');
|
||||
return true;
|
||||
} catch (error) {
|
||||
ERR('Error removing device:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Create remove device button
|
||||
function createRemoveDeviceButton(deviceId) {
|
||||
const button = document.createElement('button');
|
||||
button.setAttribute('is', 'paper-icon-button-light');
|
||||
button.className = 'sessionCardButton btnSessionRemoveDevice paper-icon-button-light';
|
||||
button.setAttribute('title', 'Remove Device');
|
||||
|
||||
const iconSpan = document.createElement('span');
|
||||
iconSpan.className = 'material-icons';
|
||||
iconSpan.setAttribute('aria-hidden', 'true');
|
||||
iconSpan.textContent = 'delete';
|
||||
|
||||
button.appendChild(iconSpan);
|
||||
|
||||
// Add click handler
|
||||
button.addEventListener('click', async (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
if (!confirm('Are you sure you want to remove this device?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
button.disabled = true;
|
||||
const originalTitle = button.getAttribute('title');
|
||||
button.setAttribute('title', 'Removing...');
|
||||
|
||||
const success = await removeDevice(deviceId);
|
||||
|
||||
if (success) {
|
||||
// Remove the session container from the DOM
|
||||
const sessionContainer = button.closest('.activeSession');
|
||||
if (sessionContainer) {
|
||||
sessionContainer.style.transition = 'opacity 0.3s';
|
||||
sessionContainer.style.opacity = '0';
|
||||
setTimeout(() => {
|
||||
sessionContainer.remove();
|
||||
}, 300);
|
||||
}
|
||||
|
||||
// Show success message if toaster is available
|
||||
if (window.KefinTweaksToaster) {
|
||||
window.KefinTweaksToaster.show('Device removed successfully', 'success');
|
||||
}
|
||||
} else {
|
||||
button.disabled = false;
|
||||
button.setAttribute('title', originalTitle);
|
||||
|
||||
// Show error message if toaster is available
|
||||
if (window.KefinTweaksToaster) {
|
||||
window.KefinTweaksToaster.show('Failed to remove device', 'error');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return button;
|
||||
}
|
||||
|
||||
// Process a single session container
|
||||
async function processSessionContainer(container, sessions) {
|
||||
// Check if button already exists
|
||||
const existingButton = container.querySelector('.btnSessionRemoveDevice');
|
||||
if (existingButton) {
|
||||
LOG('Remove device button already exists for this session');
|
||||
return;
|
||||
}
|
||||
|
||||
// Get session ID from container ID
|
||||
const containerId = container.id;
|
||||
if (!containerId || !containerId.startsWith('session')) {
|
||||
WARN('Container ID does not match expected format:', containerId);
|
||||
return;
|
||||
}
|
||||
|
||||
const sessionId = containerId.substring(7); // Remove "session" prefix
|
||||
if (!sessionId) {
|
||||
WARN('Could not extract session ID from container ID:', containerId);
|
||||
return;
|
||||
}
|
||||
|
||||
LOG(`Processing session container with ID: ${sessionId}`);
|
||||
|
||||
// Find the sessionCardButtons container
|
||||
const buttonsContainer = container.querySelector('.sessionCardButtons');
|
||||
if (!buttonsContainer) {
|
||||
WARN('sessionCardButtons container not found');
|
||||
return;
|
||||
}
|
||||
|
||||
// Fetch session data to get device ID
|
||||
const sessionData = await getSessionData(sessionId, sessions);
|
||||
if (!sessionData || !sessionData.DeviceId) {
|
||||
WARN('Could not get device ID from session data');
|
||||
return;
|
||||
}
|
||||
|
||||
const deviceId = sessionData.DeviceId;
|
||||
LOG(`Found device ID: ${deviceId}`);
|
||||
|
||||
// Create and add remove device button
|
||||
const removeButton = createRemoveDeviceButton(deviceId);
|
||||
buttonsContainer.appendChild(removeButton);
|
||||
|
||||
LOG('Remove device button added successfully');
|
||||
}
|
||||
|
||||
// Process all active session containers on the dashboard
|
||||
async function processActiveSessions() {
|
||||
const dashboardPage = document.querySelector('#dashboardPage:not(.hide)');
|
||||
if (!dashboardPage) {
|
||||
return;
|
||||
}
|
||||
|
||||
const activeDevicesContainer = dashboardPage.querySelector('.activeDevices');
|
||||
if (!activeDevicesContainer) {
|
||||
return;
|
||||
}
|
||||
|
||||
const activeSessions = activeDevicesContainer.querySelectorAll('.activeSession');
|
||||
LOG(`Found ${activeSessions.length} active session containers`);
|
||||
|
||||
if (activeSessions.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
let sessions = null;
|
||||
|
||||
if (window.ApiClient && window.ApiClient.getSessions) {
|
||||
sessions = await window.ApiClient.getSessions();
|
||||
}
|
||||
|
||||
if (!sessions) {
|
||||
WARN('Failed to get sessions');
|
||||
return;
|
||||
}
|
||||
|
||||
// Process each session container
|
||||
for (const sessionContainer of activeSessions) {
|
||||
await processSessionContainer(sessionContainer, sessions);
|
||||
}
|
||||
}
|
||||
|
||||
// Poll for activeDevicesContainer and process sessions
|
||||
function pollForActiveDevicesContainer(maxAttempts = 50, interval = 500) {
|
||||
// Clear any existing intervals
|
||||
if (pollingInterval) {
|
||||
clearInterval(pollingInterval);
|
||||
}
|
||||
if (continuousPollingInterval) {
|
||||
clearInterval(continuousPollingInterval);
|
||||
}
|
||||
|
||||
let attempts = 0;
|
||||
|
||||
pollingInterval = setInterval(async () => {
|
||||
attempts++;
|
||||
|
||||
const dashboardPage = document.querySelector('#dashboardPage:not(.hide)');
|
||||
if (!dashboardPage) {
|
||||
if (attempts >= maxAttempts) {
|
||||
clearInterval(pollingInterval);
|
||||
pollingInterval = null;
|
||||
LOG('Dashboard page not found after max attempts');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const activeDevicesContainer = dashboardPage.querySelector('.activeDevices');
|
||||
if (!activeDevicesContainer) {
|
||||
if (attempts >= maxAttempts) {
|
||||
clearInterval(pollingInterval);
|
||||
pollingInterval = null;
|
||||
LOG('Active devices container not found after max attempts');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Found the container, check for active sessions
|
||||
clearInterval(pollingInterval);
|
||||
pollingInterval = null;
|
||||
LOG('Active devices container found, checking for sessions');
|
||||
|
||||
// Check if there are any active session containers
|
||||
const activeSessions = activeDevicesContainer.querySelectorAll('.activeSession');
|
||||
|
||||
if (activeSessions.length > 0) {
|
||||
LOG(`Found ${activeSessions.length} active session containers, processing and stopping polling`);
|
||||
|
||||
// Process all existing sessions
|
||||
await processActiveSessions();
|
||||
|
||||
// Stop polling once we've found and processed sessions
|
||||
LOG('Sessions processed, polling stopped');
|
||||
return;
|
||||
}
|
||||
|
||||
// No sessions found yet, continue polling periodically
|
||||
if (!continuousPollingInterval) {
|
||||
continuousPollingInterval = setInterval(async () => {
|
||||
const currentDashboardPage = document.querySelector('#dashboardPage:not(.hide)');
|
||||
if (!currentDashboardPage) {
|
||||
// Dashboard page no longer visible, stop continuous polling
|
||||
clearInterval(continuousPollingInterval);
|
||||
continuousPollingInterval = null;
|
||||
// Restart initial polling
|
||||
pollForActiveDevicesContainer();
|
||||
return;
|
||||
}
|
||||
|
||||
const currentActiveDevicesContainer = currentDashboardPage.querySelector('.activeDevices');
|
||||
if (!currentActiveDevicesContainer) {
|
||||
return;
|
||||
}
|
||||
|
||||
const currentActiveSessions = currentActiveDevicesContainer.querySelectorAll('.activeSession');
|
||||
if (currentActiveSessions.length > 0) {
|
||||
LOG(`Found ${currentActiveSessions.length} active session containers, processing and stopping polling`);
|
||||
|
||||
// Stop continuous polling
|
||||
clearInterval(continuousPollingInterval);
|
||||
continuousPollingInterval = null;
|
||||
|
||||
// Process all sessions
|
||||
await processActiveSessions();
|
||||
|
||||
LOG('Sessions processed, polling stopped');
|
||||
}
|
||||
}, interval * 2); // Poll less frequently after initial find
|
||||
}
|
||||
}, interval);
|
||||
}
|
||||
|
||||
// Main initialization
|
||||
function initializeDashboard() {
|
||||
// Start polling for activeDevicesContainer
|
||||
pollForActiveDevicesContainer();
|
||||
}
|
||||
|
||||
// Start initialization
|
||||
initializeDeviceManager();
|
||||
})();
|
||||
|
||||
@@ -195,7 +195,6 @@
|
||||
|
||||
// Get the item promise once - will be shared across all handlers
|
||||
// The promise is cached/fetched by getCurrentItem(), ensuring only one API call
|
||||
LOG('Getting current item for ID:', currentItemId);
|
||||
const itemPromise = getCurrentItem();
|
||||
|
||||
handlers.forEach((config) => {
|
||||
Reference in New Issue
Block a user