diff --git a/movie_selector.py b/movie_selector.py index fd037f4..e31c3ef 100644 --- a/movie_selector.py +++ b/movie_selector.py @@ -53,6 +53,7 @@ USE_NEXT_BUTTON = True PLEX_AVAILABLE = False JELLYFIN_AVAILABLE = False +MOBILE_TRUNCATION = False # Other globals all_plex_unwatched_movies = [] @@ -69,7 +70,7 @@ def load_settings(): global FEATURE_SETTINGS, CLIENT_SETTINGS, APPLE_TV_SETTINGS global LG_TV_SETTINGS, PLEX_SETTINGS, JELLYFIN_SETTINGS global HOMEPAGE_MODE, USE_LINKS, USE_FILTER, USE_WATCH_BUTTON, USE_NEXT_BUTTON - global PLEX_AVAILABLE, JELLYFIN_AVAILABLE + global PLEX_AVAILABLE, JELLYFIN_AVAILABLE, MOBILE_TRUNCATION # Load all settings first FEATURE_SETTINGS = settings.get('features', {}) @@ -85,6 +86,7 @@ def load_settings(): USE_FILTER = FEATURE_SETTINGS.get('use_filter', True) USE_WATCH_BUTTON = FEATURE_SETTINGS.get('use_watch_button', True) USE_NEXT_BUTTON = FEATURE_SETTINGS.get('use_next_button', True) + MOBILE_TRUNCATION = FEATURE_SETTINGS.get('mobile_truncation', False) # Update service availability flags PLEX_AVAILABLE = ( @@ -501,6 +503,7 @@ def index(): use_filter=USE_FILTER, use_watch_button=USE_WATCH_BUTTON, use_next_button=USE_NEXT_BUTTON, + mobile_truncation=MOBILE_TRUNCATION, settings_disabled=settings.get('system', {}).get('disable_settings', False) ) @@ -1198,8 +1201,8 @@ def get_plex_token(): # Store in global dict _plex_pin_logins[client_id] = pin_login - - logger.info(f"Plex auth initiated with PIN: {pin_login.pin}") + + logger.info("Plex auth initiated with PIN: ****") return jsonify({ "auth_url": "https://plex.tv/link", diff --git a/static/js/script.js b/static/js/script.js index fb1e897..cdfde4d 100644 --- a/static/js/script.js +++ b/static/js/script.js @@ -45,19 +45,35 @@ document.addEventListener('DOMContentLoaded', async function() { setupEventListeners(); checkAndLoadCache(); try { - // Check current client configuration - const response = await fetch('/devices'); - const devices = await response.json(); - const powerButton = document.getElementById("btn_power"); - if (powerButton) { - powerButton.style.display = devices.length > 0 ? 'flex' : 'none'; - } + // Check current client configuration + const response = await fetch('/devices'); + const devices = await response.json(); + const powerButton = document.getElementById("btn_power"); + const nextButton = document.getElementById("btn_next_movie"); + + if (powerButton && nextButton) { + if (devices.length > 0) { + powerButton.style.display = 'flex'; + nextButton.style.flex = ''; + } else { + powerButton.style.display = 'none'; + if (window.matchMedia('(max-width: 767px)').matches) { + nextButton.style.flex = '0 0 100%'; + nextButton.style.marginRight = '0'; + } + } + } } catch (error) { - console.error("Error checking devices:", error); - const powerButton = document.getElementById("btn_power"); - if (powerButton) { + console.error("Error checking devices:", error); + const powerButton = document.getElementById("btn_power"); + const nextButton = document.getElementById("btn_next_movie"); + if (powerButton && nextButton) { powerButton.style.display = 'none'; - } + if (window.matchMedia('(max-width: 767px)').matches) { + nextButton.style.flex = '0 0 100%'; + nextButton.style.marginRight = '0'; + } + } } await syncTraktWatched(false); startVersionChecker(); @@ -500,7 +516,7 @@ function updateMovieDisplay(movieData) { return; } - console.log('Movie data received:', movieData); +// console.log('Movie data received:', movieData); hideMessage(); @@ -1093,37 +1109,56 @@ class ExpandableText { } reset() { - if (!this.element) return; - this.element.removeEventListener('click', this.boundToggle); - this.element.classList.remove('truncated', 'expanded'); - this.element.style.webkitLineClamp = '1'; - this.element.style.display = '-webkit-box'; - this.element.style.maxHeight = ''; - this.element.style.cursor = 'default'; - this.state.expanded = false; + if (!this.element) return; + this.element.removeEventListener('click', this.boundToggle); + this.element.classList.remove('truncated', 'expanded'); + + // Check for mobile and truncation setting + const isMobileOrPWA = window.matchMedia('(max-width: 767px)').matches; + if (isMobileOrPWA) { + // On mobile, respect the MOBILE_TRUNCATION setting + if (window.MOBILE_TRUNCATION) { + this.element.style.webkitLineClamp = '1'; + this.element.style.display = '-webkit-box'; + } else { + this.element.style.webkitLineClamp = 'unset'; + this.element.style.display = 'block'; + } + } else { + // On desktop, always use truncation + this.element.style.webkitLineClamp = '1'; + this.element.style.display = '-webkit-box'; + } + + this.element.style.maxHeight = ''; + this.element.style.cursor = 'default'; + this.state.expanded = false; } checkTruncation() { - if (!this.element) return; + if (!this.element) return; - requestAnimationFrame(() => { + requestAnimationFrame(() => { void this.element.offsetHeight; + const isMobileOrPWA = window.matchMedia('(max-width: 767px)').matches; + + // If on mobile and truncation is disabled, don't truncate + if (isMobileOrPWA && !window.MOBILE_TRUNCATION) { + this.state.truncated = false; + this.element.classList.remove('truncated'); + this.element.style.cursor = 'default'; + return; + } + const truncated = this.element.scrollHeight > this.element.clientHeight; this.state.truncated = truncated; if (truncated) { - this.element.classList.add('truncated'); - this.element.style.cursor = 'pointer'; + this.element.classList.add('truncated'); + this.element.style.cursor = 'pointer'; } - - console.log('Truncation check:', { - scrollHeight: this.element.scrollHeight, - clientHeight: this.element.clientHeight, - isTruncated: truncated, - text: this.element.textContent.substring(0, 50) + '...' - }); - }); + }); } bindEvents() { @@ -1131,6 +1166,35 @@ class ExpandableText { this.element.addEventListener('click', this.boundToggle); } + checkTruncation() { + if (!this.element) return; + + requestAnimationFrame(() => { + void this.element.offsetHeight; + + // Add check for mobile and truncation setting + const isMobileOrPWA = window.matchMedia('(max-width: 768px)').matches || window.navigator.standalone; + const shouldTruncate = !isMobileOrPWA || window.MOBILE_TRUNCATION; + + const truncated = shouldTruncate && this.element.scrollHeight > this.element.clientHeight; + this.state.truncated = truncated; + + if (truncated) { + this.element.classList.add('truncated'); + this.element.style.cursor = 'pointer'; + } + + console.log('Truncation check:', { + scrollHeight: this.element.scrollHeight, + clientHeight: this.element.clientHeight, + isTruncated: truncated, + isMobile: isMobileOrPWA, + truncationEnabled: window.MOBILE_TRUNCATION, + text: this.element.textContent.substring(0, 50) + '...' + }); + }); + } + toggle(e) { if (e) e.stopPropagation(); if (!this.state.truncated || !this.element) return; diff --git a/static/js/settings.js b/static/js/settings.js index 548a4e3..6607247 100644 --- a/static/js/settings.js +++ b/static/js/settings.js @@ -411,7 +411,6 @@ document.addEventListener('DOMContentLoaded', function() { } function renderSettingsSection(title, settings, envOverrides, fields) { - console.log('Current envOverrides:', envOverrides); const section = document.createElement('div'); section.className = 'settings-section'; @@ -427,6 +426,13 @@ document.addEventListener('DOMContentLoaded', function() { label.textContent = field.label; fieldContainer.appendChild(label); + if (field.description) { + const description = document.createElement('div'); + description.className = 'setting-description'; + description.innerHTML = field.description; + fieldContainer.appendChild(description); + } + if (field.key === 'trakt.connect') { createTraktIntegration(fieldContainer); section.appendChild(fieldContainer); @@ -490,11 +496,7 @@ document.addEventListener('DOMContentLoaded', function() { const value = getNestedValue(settings, field.key); let isOverridden = getNestedValue(envOverrides, field.key); - console.log(`Field ${field.key}:`, { - value, - isOverridden, - envOverrides: envOverrides - }); + console.log(`Field ${field.key}: configured`); const isIntegrationToggle = ( field.key === 'overseerr.enabled' || @@ -851,7 +853,13 @@ document.addEventListener('DOMContentLoaded', function() { { key: 'features.use_filter', label: 'Enable Filters', type: 'switch' }, { key: 'features.use_watch_button', label: 'Enable Watch Button', type: 'switch' }, { key: 'features.use_next_button', label: 'Enable Next Button', type: 'switch' }, - { key: 'features.homepage_mode', label: 'Homepage Mode', type: 'switch' } + { key: 'features.mobile_truncation', label: 'Enable Mobile Description Truncation', type: 'switch' }, + { + key: 'features.homepage_mode', + label: 'Homepage Mode', + type: 'switch', + description: 'Provides a simplified, non-interactive display format ideal for Homepage iframe integration. Removes buttons, links, and keeps movie descriptions fully expanded.' + } ] }, { @@ -1637,7 +1645,6 @@ document.addEventListener('DOMContentLoaded', function() { } const data = await response.json(); - console.log("Auth data received:", data); const authWindow = window.open( data.auth_url, @@ -1669,7 +1676,7 @@ document.addEventListener('DOMContentLoaded', function() { const statusResponse = await fetch(`/api/plex/check_auth/${data.client_id}`); const statusData = await statusResponse.json(); - console.log("Status check:", statusData); + console.log("Auth status check completed"); if (statusData.token) { clearInterval(checkAuth); diff --git a/static/style/settings.css b/static/style/settings.css index 7c7e780..2d0fd89 100644 --- a/static/style/settings.css +++ b/static/style/settings.css @@ -930,3 +930,22 @@ width: 20px; height: 20px; } + +.setting-description { + font-size: 0.9em; + color: #EAEAEC; + margin: 4px 0 8px 0; + opacity: 0.8; + line-height: 1.4; +} + +.setting-description a { + color: #E5A00D; + text-decoration: none; + transition: color 0.2s; +} + +.setting-description a:hover { + color: #F8D68B; + text-decoration: underline; +} diff --git a/static/style/style.css b/static/style/style.css index 98318d7..b961fd8 100644 --- a/static/style/style.css +++ b/static/style/style.css @@ -1102,12 +1102,15 @@ main { flex: 0 0 calc(50% - 5px); } - #btn_next_movie { - margin-right: 10px; /* Space between the buttons */ + #btn_power[style*="display: none"] ~ #btn_next_movie, + #btn_power.hidden ~ #btn_next_movie { + flex: 0 0 100% !important; + max-width: 100% !important; + margin-right: 0 !important; } - #btn_power { - margin-right: 0; + #btn_next_movie { + margin-right: 10px; /* Space between the buttons */ } #switch_service { @@ -1125,12 +1128,6 @@ main { justify-content: center !important; } -/* #switch_service .switch-icon { - margin-left: 8px !important; - width: 20px !important; - height: 20px !important; - } */ - #switch_service .switch-icon { position: absolute; right: 12px; @@ -1187,7 +1184,7 @@ main { } /* Description Adjustments */ - #description { + body.homepage-mode #description { display: block !important; -webkit-line-clamp: unset !important; -webkit-box-orient: unset !important; @@ -1196,9 +1193,10 @@ main { padding-right: 0 !important; } - #description.truncated::after { + body.homepage-mode #description.truncated::after { display: none !important; } + } /* PWA Specific Styles */ @@ -1277,12 +1275,12 @@ body.in-iframe .main-nav { } body.in-iframe main { - padding-top: 0; + padding-top: 0; } @media all and (display-mode: standalone) { body.in-iframe main { - padding-top: 0; + padding-top: 0; } } @@ -2438,12 +2436,12 @@ body.homepage-mode #description.truncated::after { .trakt-confirm-dialog .dialog-content p, .trakt-confirm-dialog .version-info { color: #EAEAEC; - font-weight: 300; + font-weight: 300; } .trakt-confirm-dialog .changelog-content { color: #EAEAEC; - font-weight: 300; + font-weight: 300; white-space: pre-line; line-height: 1.6; } diff --git a/utils/fetch_movie_links.py b/utils/fetch_movie_links.py index 96e9e39..5f50109 100644 --- a/utils/fetch_movie_links.py +++ b/utils/fetch_movie_links.py @@ -59,7 +59,7 @@ def initialize_services(): # Initialize Plex plex = PlexServer(plex_url, plex_token) - logger.info(f"Plex initialized with URL: {plex_url}, Libraries: {PLEX_MOVIE_LIBRARIES}") + logger.info(f"Plex initialized with URL: {plex_url}, Libraries: [REDACTED]") except Exception as e: logger.error(f"Error initializing Plex: {e}") PLEX_AVAILABLE = False diff --git a/utils/jellyfin_service.py b/utils/jellyfin_service.py index 32e503e..2623c90 100644 --- a/utils/jellyfin_service.py +++ b/utils/jellyfin_service.py @@ -317,7 +317,7 @@ def get_movie_data(self, movie): "description": movie.get('Overview', ''), "genres": movie.get('Genres', []), "poster": f"{self.server_url}/Items/{movie['Id']}/Images/Primary?api_key={self.api_key}", - "background": f"{self.server_url}/Items/{movie['Id']}/Images/Backdrop?api_key={self.api_key}", + "background": f"{self.server_url}/Items/{movie['Id']}/Images/Backdrop?api_key={self.api_key}" if movie.get('BackdropImageTags') else None, "ProviderIds": movie.get('ProviderIds', {}), "contentRating": movie.get('OfficialRating', ''), "videoFormat": video_format, diff --git a/utils/plex_service.py b/utils/plex_service.py index 8920d9e..3867450 100644 --- a/utils/plex_service.py +++ b/utils/plex_service.py @@ -18,7 +18,7 @@ class PlexService: def __init__(self, url=None, token=None, libraries=None): logger.info("Initializing PlexService") - logger.info(f"Parameters - URL: {bool(url)}, Token: {bool(token)}, Libraries: {libraries}") + logger.info(f"Parameters - URL: {bool(url)}, Token: {bool(token)}, Libraries: [REDACTED]") # First try settings (from parameters) self.PLEX_URL = url diff --git a/utils/settings/config.py b/utils/settings/config.py index e8aa26a..d0a8998 100644 --- a/utils/settings/config.py +++ b/utils/settings/config.py @@ -27,6 +27,7 @@ 'use_filter': True, 'use_watch_button': True, 'use_next_button': True, + 'mobile_truncation': False, 'homepage_mode': False, 'timezone': 'UTC', 'default_poster_text': '', @@ -84,6 +85,7 @@ 'USE_FILTER': ('features', 'use_filter', lambda x: x.upper() == 'TRUE'), 'USE_WATCH_BUTTON': ('features', 'use_watch_button', lambda x: x.upper() == 'TRUE'), 'USE_NEXT_BUTTON': ('features', 'use_next_button', lambda x: x.upper() == 'TRUE'), + 'ENABLE_MOBILE_TRUNCATION': ('features', 'mobile_truncation', lambda x: x.upper() == 'TRUE'), # Overseerr 'OVERSEERR_URL': ('overseerr', 'url', str), diff --git a/utils/version.py b/utils/version.py index 0f39c7c..b32bc07 100644 --- a/utils/version.py +++ b/utils/version.py @@ -1 +1 @@ -VERSION = "3.1" +VERSION = "3.1.1" diff --git a/web/index.html b/web/index.html index 071f449..279462a 100644 --- a/web/index.html +++ b/web/index.html @@ -197,6 +197,7 @@

Building Movie Library Cache

window.USE_FILTER = {{ 'true' if use_filter else 'false' }}; window.USE_WATCH_BUTTON = {{ 'true' if use_watch_button else 'false' }}; window.USE_NEXT_BUTTON = {{ 'true' if use_next_button else 'false' }}; + window.MOBILE_TRUNCATION = {{ 'true' if mobile_truncation else 'false' }};