// KefinTweaks Thumbnail Scrubber Configuration UI // Modular configuration modal for managing thumbnail scrubber settings (function() { 'use strict'; const LOG = (...args) => console.log('[KefinTweaks ThumbnailScrubber Config]', ...args); const ERR = (...args) => console.error('[KefinTweaks ThumbnailScrubber Config]', ...args); const MODAL_ID = 'kefin-thumbnailscrubber-config'; /** * Load configuration from window.KefinTweaksConfig */ async function loadConfig() { const config = window.KefinTweaksConfig || {}; return config.thumbnailScrubber || {}; } /** * Save configuration to JS Injector */ async function saveConfig(thumbnailScrubberConfig) { try { // Ensure window.KefinTweaksConfig exists if (!window.KefinTweaksConfig) { window.KefinTweaksConfig = {}; } // Update thumbnailScrubber config window.KefinTweaksConfig.thumbnailScrubber = thumbnailScrubberConfig; // Save to JS Injector using utils if (window.KefinTweaksUtils && window.KefinTweaksUtils.saveConfigToJavaScriptInjector) { await window.KefinTweaksUtils.saveConfigToJavaScriptInjector(); LOG('Configuration saved to JS Injector'); return true; } else { // Fallback: use findJavaScriptInjectorPlugin and saveConfigToJavaScriptInjector from configuration.js if (window.KefinTweaksConfiguration && window.KefinTweaksConfiguration.findJavaScriptInjectorPlugin) { const pluginId = await window.KefinTweaksConfiguration.findJavaScriptInjectorPlugin(); if (pluginId) { await window.KefinTweaksConfiguration.saveConfigToJavaScriptInjector(window.KefinTweaksConfig); LOG('Configuration saved to JS Injector (fallback)'); return true; } } ERR('saveConfigToJavaScriptInjector not available'); return false; } } catch (error) { ERR('Error saving config:', error); return false; } } /** * Build configuration HTML */ function buildConfigHTML(config) { const previewOnHover = config.PreviewOnHover === true; const useVideoStream = config.UseVideoStream !== false; let fallbackAfterMs = 500; if (Number.isFinite(config.FallbackAfterMs * 1) && config.FallbackAfterMs >= 0) { fallbackAfterMs = config.FallbackAfterMs * 1; } else if (Number.isFinite(config.FallbackAfterSeconds * 1) && config.FallbackAfterSeconds >= 0) { fallbackAfterMs = config.FallbackAfterSeconds * 1000; } const hoverDelay = Number.isFinite(config.HOVER_PREVIEW_TIMEOUT_MS * 1) ? config.HOVER_PREVIEW_TIMEOUT_MS * 1 : (Number.isFinite(config.HOVER_TIMEOUT * 1) ? config.HOVER_TIMEOUT * 1 : 800); const hoverSpeed = Number.isFinite(config.HOVER_PREVIEW_FRAME_MS * 1) ? config.HOVER_PREVIEW_FRAME_MS * 1 : 250; const scrubDelay = Number.isFinite(config.SCRUB_ACTIVATION_DELAY_MS * 1) ? config.SCRUB_ACTIVATION_DELAY_MS * 1 : 300; return `
Preview On Hover
When enabled, hovering over an eligible card will automatically cycle trickplay thumbnails as the card background after a short delay, without needing to scrub in the progress zone.
Use Video Stream Preview
When enabled, hover preview plays a muted video stream (/Videos/{id}/stream) instead of cycling trickplay images as the card background.
Fallback After (ms)
When Use Video Stream is on, how long to wait for the direct stream to start playing before falling back to trickplay images. Set to 0 to disable the timeout (play errors still fall back to trickplay).
Delay Before Previewing On Hover (ms)
How long the cursor must stay over a card before the passive preview starts. Set to 0 to start immediately when hovering a card.
Hover Preview Speed (ms between frames)
Interval in milliseconds between each trickplay frame during hover preview. Leave at 0 to use the default trickplay interval from the server.
Delay Before Activating Scrubbing (ms)
How long the cursor must stay in the scrub zone at the bottom of the card before the scrubber UI activates.
`; } /** * Collect configuration from form */ function collectConfig() { const previewOnHover = document.getElementById('thumbnailScrubber_PreviewOnHover')?.checked === true; const useVideoStream = document.getElementById('thumbnailScrubber_UseVideoStream')?.checked === true; const hoverDelayInput = document.getElementById('thumbnailScrubber_HOVER_PREVIEW_TIMEOUT_MS'); const scrubDelayInput = document.getElementById('thumbnailScrubber_SCRUB_ACTIVATION_DELAY_MS'); const hoverSpeedInput = document.getElementById('thumbnailScrubber_HOVER_PREVIEW_FRAME_MS'); const fallbackAfterInput = document.getElementById('thumbnailScrubber_FallbackAfterMs'); let hoverDelay = hoverDelayInput ? parseInt(hoverDelayInput.value, 10) : 800; if (!Number.isFinite(hoverDelay) || hoverDelay < 0) { hoverDelay = 800; } let scrubDelay = scrubDelayInput ? parseInt(scrubDelayInput.value, 10) : 300; if (!Number.isFinite(scrubDelay) || scrubDelay < 0) { scrubDelay = 300; } let hoverSpeed = hoverSpeedInput ? parseInt(hoverSpeedInput.value, 10) : 0; if (!Number.isFinite(hoverSpeed) || hoverSpeed < 0) { hoverSpeed = 0; } let fallbackAfterMs = fallbackAfterInput ? parseInt(fallbackAfterInput.value, 10) : 500; if (!Number.isFinite(fallbackAfterMs) || fallbackAfterMs < 0) { fallbackAfterMs = 500; } return { PreviewOnHover: previewOnHover, UseVideoStream: useVideoStream, FallbackAfterMs: fallbackAfterMs, // Delay before previewing on hover HOVER_PREVIEW_TIMEOUT_MS: hoverDelay, HOVER_TIMEOUT: hoverDelay, // legacy key // Hover preview speed (ms between frames) HOVER_PREVIEW_FRAME_MS: hoverSpeed, // Scrub activation delay SCRUB_ACTIVATION_DELAY_MS: scrubDelay }; } /** * Open configuration modal */ async function openConfigModal() { try { // Load config const config = await loadConfig(); // Build content const content = document.createElement('div'); content.innerHTML = buildConfigHTML(config); // Build footer const footer = document.createElement('div'); footer.style.display = 'flex'; footer.style.gap = '0.75em'; footer.style.justifyContent = 'flex-end'; footer.innerHTML = ` `; // Create modal window.ModalSystem.create({ id: MODAL_ID, title: 'Thumbnail Scrubber Configuration', content: content, footer: footer, closeOnBackdrop: true, closeOnEscape: true, showCloseButton: true, dialogStyle: window.innerWidth >= 900 ? { maxWidth: '90vw', width: '800px', height: 'auto' } : undefined, onOpen: (modal) => { // Save button const saveBtn = modal.dialogFooter.querySelector('#save-thumbnailscrubber-config-btn'); if (saveBtn) { saveBtn.addEventListener('click', async () => { const thumbnailScrubberConfig = collectConfig(); const success = await saveConfig(thumbnailScrubberConfig); if (success) { if (window.KefinTweaksToaster && window.KefinTweaksToaster.toast) { window.KefinTweaksToaster.toast('Thumbnail Scrubber configuration saved!'); } window.ModalSystem.close(MODAL_ID); } else { alert('Error saving configuration. Please ensure the JavaScript Injector plugin is installed and you have administrator permissions.'); } }); } } }); LOG('Thumbnail Scrubber configuration modal opened'); } catch (error) { ERR('Error opening thumbnail scrubber config modal:', error); alert('Error opening configuration: ' + error.message); } } // Export window.KefinTweaksFeatureConfigs = window.KefinTweaksFeatureConfigs || {}; window.KefinTweaksFeatureConfigs.thumbnailScrubber = { openConfigModal }; console.log('[KefinTweaks ThumbnailScrubber Config] Script loaded'); })();