// KefinTweaks Home Screen Configuration UI // Modular configuration modal for managing home screen sections // Uses the new HomeScreenSection format with queries array (function() { 'use strict'; const LOG = (...args) => console.log('[KefinTweaks HomeScreen Config]', ...args); const WARN = (...args) => console.warn('[KefinTweaks HomeScreen Config]', ...args); const ERR = (...args) => console.error('[KefinTweaks HomeScreen Config]', ...args); // Modal IDs const MAIN_MODAL_ID = 'kefin-homescreen-config-main'; const SECTION_EDITOR_MODAL_ID = 'kefin-homescreen-section-editor'; const DISCOVERY_EDITOR_MODAL_ID = 'kefin-homescreen-discovery-editor'; // Supported additional query options (matching configuration.js) const SUPPORTED_QUERY_OPTIONS = { Ids: { label: 'Item IDs', type: 'array', hint: 'Comma-separated GUIDs' }, ParentId: { label: 'Parent ID', type: 'string', hint: 'GUID of parent item' }, ExcludeItemIds: { label: 'Exclude Item IDs', type: 'array', hint: 'Comma-separated GUIDs' }, GenreIds: { label: 'Genre IDs', type: 'array', hint: 'Comma-separated GUIDs' }, PersonIds: { label: 'Person IDs', type: 'array', hint: 'Comma-separated GUIDs' }, StudioIds: { label: 'Studio IDs', type: 'array', hint: 'Comma-separated GUIDs' }, Fields: { label: 'Fields', type: 'array', hint: 'Comma-separated field names' }, MinPremiereDate: { label: 'Min Premiere Date', type: 'date', hint: 'YYYY-MM-DD' }, MaxPremiereDate: { label: 'Max Premiere Date', type: 'date', hint: 'YYYY-MM-DD' }, MinDateLastSaved: { label: 'Min Date Last Saved', type: 'date', hint: 'YYYY-MM-DD' }, MinDateLastSavedForUser: { label: 'Min Date Last Saved For User', type: 'date', hint: 'YYYY-MM-DD' }, IndexNumber: { label: 'Index Number', type: 'number', hint: 'Episode/Season index' }, ParentIndexNumber: { label: 'Parent Index Number', type: 'number', hint: 'Season index' }, MinCommunityRating: { label: 'Min Community Rating', type: 'number', hint: '0-10' }, MinCriticRating: { label: 'Min Critic Rating', type: 'number', hint: '0-10' }, IsFavorite: { label: 'Is Favorite', type: 'boolean' }, IsPlayed: { label: 'Is Played', type: 'boolean' }, IsUnplayed: { label: 'Is Unplayed', type: 'boolean' }, IsMissing: { label: 'Is Missing', type: 'boolean' }, IsUnaired: { label: 'Is Unaired', type: 'boolean' }, HasThemeSong: { label: 'Has Theme Song', type: 'boolean' }, HasThemeVideo: { label: 'Has Theme Video', type: 'boolean' }, HasSubtitles: { label: 'Has Subtitles', type: 'boolean' }, HasSpecialFeature: { label: 'Has Special Feature', type: 'boolean' }, HasTrailer: { label: 'Has Trailer', type: 'boolean' }, HasParentalRating: { label: 'Has Parental Rating', type: 'boolean' }, IsHd: { label: 'Is HD', type: 'boolean' }, Is4K: { label: 'Is 4K', type: 'boolean' }, HasOverview: { label: 'Has Overview', type: 'boolean' }, HasOfficialRating: { label: 'Has Official Rating', type: 'boolean' }, Recursive: { label: 'Recursive', type: 'boolean', default: true }, MaxOfficialRating: { label: 'Max Official Rating', type: 'string', hint: 'e.g., PG-13, TV-MA' }, MinOfficialRating: { label: 'Min Official Rating', type: 'string', hint: 'e.g., PG, TV-14' }, NameStartsWith: { label: 'Name Starts With', type: 'string' }, NameStartsWithOrGreater: { label: 'Name Starts With Or Greater', type: 'string' }, NameLessThan: { label: 'Name Less Than', type: 'string' }, LocationTypes: { label: 'Location Types', type: 'array', hint: 'FileSystem, Remote, Virtual, Offline' }, ExcludeLocationTypes: { label: 'Exclude Location Types', type: 'array', hint: 'FileSystem, Remote, Virtual, Offline' }, ExcludeItemTypes: { label: 'Exclude Item Types', type: 'array', hint: 'Movie, Series, Episode, etc.' }, ImageTypes: { label: 'Image Types', type: 'array', hint: 'Primary, Backdrop, Thumb, Logo, etc.' }, OfficialRatings: { label: 'Official Ratings', type: 'array', hint: 'PG, PG-13, R, etc.' }, Studios: { label: 'Studios', type: 'array', hint: 'Pipe-delimited names' }, Artists: { label: 'Artists', type: 'array', hint: 'Pipe-delimited names' }, Albums: { label: 'Albums', type: 'array', hint: 'Pipe-delimited names' }, SeriesStatus: { label: 'Series Status', type: 'array', hint: 'Continuing, Ended, Unreleased' }, Years: { label: 'Years', type: 'array', hint: 'Comma-separated years' }, EnableResumable: { label: 'Enable Resumable', type: 'boolean' }, EnableUserData: { label: 'Enable User Data', type: 'boolean' } }; const CARD_FORMATS = ['Poster', 'Thumb', 'Backdrop', 'Square', 'Random']; const SORT_ORDERS = ['None', 'Random', 'Name', 'DateCreated', 'PremiereDate', 'CommunityRating', 'CriticRating', 'DatePlayed', 'SortName', 'PlayCount', 'PlayedPercentage', 'StartDate', 'Runtime', 'ProductionYear', 'IsPlayed', 'IsUnplayed', 'ParentIndexNumber', 'IndexNumber', 'IsFolder', 'SimilarityScore', 'SearchScore', 'DateLastContentAdded', 'SeriesDatePlayed', 'ChildCount']; const SORT_ORDER_DIRECTIONS = ['Ascending', 'Descending']; const RENDER_MODE_OPTIONS = [{ value: 'Normal', label: 'Normal' }, { value: 'Spotlight', label: 'Spotlight' }, { value: 'Random', label: 'Random' }]; const SLIDE_STATE_OPTIONS = [ { value: 'none', label: 'None' }, { value: 'dots', label: 'Dots' }, { value: 'numeric', label: 'Numeric' } ]; // Current config state let currentConfig = null; let mainModalInstance = null; let currentActiveTab = 'settings'; // Track the currently active tab let currentGlobalSettingsSubTab = 'general'; // Track active sub-tab in Global Settings (general, spotlight, discovery, cache) /** * Show toast notification */ function showToast(message, duration = '3') { if (window.KefinTweaksToaster && window.KefinTweaksToaster.toast) { window.KefinTweaksToaster.toast(message, duration); } else { // Fallback to alert if toaster not available alert(message); } } /** * Flatten section groups into a flat array of sections * @param {Array} groups - Array of HomeScreenSectionGroup objects * @returns {Array} Flat array of all sections */ function flattenSectionGroups(groups) { if (!Array.isArray(groups)) return []; const flattened = []; groups.forEach(group => { if (group && Array.isArray(group.sections)) { flattened.push(...group.sections); } }); return flattened; } /** * Get all sections from groups with metadata about which group they belong to * @param {Array} groups - Array of HomeScreenSectionGroup objects * @returns {Array} Array of objects with section and groupName */ function getAllSectionsFromGroups(groups) { if (!Array.isArray(groups)) return []; const result = []; groups.forEach(group => { if (group && Array.isArray(group.sections)) { group.sections.forEach(section => { result.push({ section: section, groupName: group.name || 'Unnamed Group', groupAuthor: group.author, groupDescription: group.description }); }); } }); return result; } /** * Render section groups with group-level and section-level checkboxes * Used by import and export modals to display sections within their groups * @param {HTMLElement} container - Container element to render into * @param {Array} sectionGroups - Array of section group objects { name, author?, description?, sections: [] } * @param {Object} options - Options object * @param {string} options.checkboxClass - CSS class for section checkboxes (e.g., 'export-custom-check', 'import-custom-check') * @param {string} options.groupCheckboxClass - CSS class for group checkboxes (optional, defaults to checkboxClass + '-group') */ function renderSectionGroupsCheckboxList(container, sectionGroups, options = {}) { if (!Array.isArray(sectionGroups) || sectionGroups.length === 0) { container.innerHTML = '