`;
}
/**
* Build toggle slider HTML (simplified version for user editor)
*/
function buildToggleSliderHTML(checked, sectionId, onToggle) {
const isEnabled = checked !== false;
return `
`;
}
/**
* Build section row HTML for order editor
*/
function buildSectionRowHTMLForEditor(section, options = {}) {
const { showUpDownButtons = false, isFirst = false, isLast = false } = options;
const isEnabled = section.enabled !== false;
const sectionId = section.id || '';
const sectionName = section.name || 'Unnamed Section';
const isJellyfin = sectionId.endsWith('-jellyfin') || section.isJellyfin === true;
// Determine section type (always lowercase for consistency with CSS)
let sectionType = section.type ? section.type.toLowerCase() : null;
if (!sectionType) {
sectionType = 'home';
}
// Disabled sections should not be draggable
const draggableAttr = isEnabled ? 'draggable="true"' : '';
// Build data attributes for first/last state
const dataAttrs = [];
if (isFirst) dataAttrs.push('data-is-first="true"');
if (isLast) dataAttrs.push('data-is-last="true"');
const dataAttrsStr = dataAttrs.join(' ');
return `
${buildToggleSliderHTML(isEnabled, sectionId)}
${sectionName.replace(/"/g, '"')}
${showUpDownButtons ? `
` : ''}
`;
}
/**
* Build group HTML for order editor
* @param {Array} enabledSections - Enabled sections to render in reorderable list
* @param {Array} disabledSections - Disabled sections to render in non-reorderable list
* @param {Object} options - Options object
*/
function buildGroupHTMLForEditor(enabledSections, disabledSections = [], options = {}) {
const { showUpDownButtons = false } = options;
const sortedEnabled = [...(enabledSections || [])].sort((a, b) => (a.order || 0) - (b.order || 0));
const sortedDisabled = [...(disabledSections || [])].sort((a, b) => (a.order || 0) - (b.order || 0));
let html = '';
// Enabled sections list (reorderable)
if (sortedEnabled.length > 0) {
html += `
${sortedEnabled.map((section, index) =>
buildSectionRowHTMLForEditor(section, {
showUpDownButtons,
isFirst: index === 0,
isLast: index === sortedEnabled.length - 1
})
).join('')}
`;
}
// Disabled sections list (non-reorderable)
// Buttons are always rendered, CSS hides them for disabled sections
if (sortedDisabled.length > 0) {
html += `
Disabled Sections
${sortedDisabled.map((section) =>
buildSectionRowHTMLForEditor(section, {
showUpDownButtons: showUpDownButtons, // Use same setting as enabled sections
isFirst: false,
isLast: false
})
).join('')}
`;
}
return html;
}
/**
* Calculate new order with enhanced logic for same-order sections
*/
function calculateNewOrderEnhanced(draggedElement, targetElement, dropPosition, getSectionOrder) {
const container = targetElement.parentElement;
const allRows = Array.from(container.querySelectorAll('.section-row'));
// Find the target's position in the DOM
const targetIndex = allRows.indexOf(targetElement);
const draggedIndex = allRows.indexOf(draggedElement);
if (dropPosition === 'above') {
// Dropped above target element
if (targetIndex === 0) {
// Dropped at the beginning - use first section order - 1
const firstSectionId = targetElement.dataset.sectionId;
const firstOrder = getSectionOrder(firstSectionId);
return Math.max(0, firstOrder - 1);
} else {
// Get preceding and following sections
const precedingElement = allRows[targetIndex - 1];
const followingElement = targetElement;
if (precedingElement && followingElement) {
const precedingId = precedingElement.dataset.sectionId;
const followingId = followingElement.dataset.sectionId;
const precedingOrder = getSectionOrder(precedingId);
const followingOrder = getSectionOrder(followingId);
// If same order, use that order
if (precedingOrder === followingOrder) {
return precedingOrder;
}
}
// Different orders or missing - use target's order - 1
const targetSectionId = targetElement.dataset.sectionId;
const targetOrder = getSectionOrder(targetSectionId);
return Math.max(0, targetOrder - 1);
}
} else {
// dropPosition === 'below'
if (targetIndex === allRows.length - 1) {
// Dropped at the end - use last section order + 1
const lastSectionId = targetElement.dataset.sectionId;
const lastOrder = getSectionOrder(lastSectionId);
return lastOrder + 1;
} else {
// Get preceding and following sections
const precedingElement = targetElement;
const followingElement = allRows[targetIndex + 1];
if (precedingElement && followingElement && followingElement !== draggedElement) {
const precedingId = precedingElement.dataset.sectionId;
const followingId = followingElement.dataset.sectionId;
const precedingOrder = getSectionOrder(precedingId);
const followingOrder = getSectionOrder(followingId);
// If same order, use that order
if (precedingOrder === followingOrder) {
return precedingOrder;
}
}
// Different orders or missing - use following section order - 1, or preceding + 1
if (followingElement && followingElement !== draggedElement) {
const nextSectionId = followingElement.dataset.sectionId;
const nextOrder = getSectionOrder(nextSectionId);
return Math.max(0, nextOrder - 1);
} else {
const targetSectionId = targetElement.dataset.sectionId;
const targetOrder = getSectionOrder(targetSectionId);
return targetOrder + 1;
}
}
}
}
/**
* Render home sections order editor
* @param {Array} sections - Array of section objects
* @param {Object} options - Options object
* @param {boolean} options.showUpDownButtons - Show up/down buttons on hover
* @param {Function} options.onOrderChange - Callback when order changes (sectionId, newOrder)
* @param {Function} options.onToggleChange - Callback when toggle changes (sectionId, enabled)
* @param {Function} options.getSectionOrder - Function to get current order of a section
* @returns {string} HTML string
*/
function renderHomeSectionsOrderEditor(sections, options = {}) {
if (!sections || !Array.isArray(sections)) {
return '
';
}
// Get unique section types (sorted for consistent display) all in lowercase
const renderedSectionTypes = Array.from(sectionTypesSet).sort().map(type => type.toLowerCase());
// Preferred Section Types order in array: Jellyfin, Home, Seasonal, Discovery, Custom
let sectionTypes = ['jellyfin', 'home', 'seasonal', 'discovery', 'custom'];
sectionTypes = sectionTypes.filter(type => renderedSectionTypes.includes(type));
// Load saved toggle states from localStorage
const savedToggleStates = getSectionTypeToggleStates();
// Build data attributes for type filtering
// Use saved states if available, otherwise use defaults (seasonal = false, others = true)
const typeFilterAttrs = sectionTypes.map(type => {
const typeLower = type.toLowerCase();
// Check if we have a saved state for this type
const hasSavedState = savedToggleStates.hasOwnProperty(typeLower);
let isEnabled;
if (hasSavedState) {
// Use saved state
isEnabled = savedToggleStates[typeLower] ? 'true' : 'false';
} else {
// Use default: seasonal is hidden by default, all others are visible
isEnabled = typeLower === 'seasonal' ? 'false' : 'true';
}
return `data-section-type-${typeLower}="${isEnabled}"`;
}).join(' ');
// Determine which types should have their toggle buttons initially enabled
const enabledTypes = sectionTypes.filter(type => {
const typeLower = type.toLowerCase();
const hasSavedState = savedToggleStates.hasOwnProperty(typeLower);
if (hasSavedState) {
return savedToggleStates[typeLower];
}
// Default: seasonal is off, others are on
return typeLower !== 'seasonal';
});
return `