Add some example sections to the community area, and ensure sections/groups cannot be imported in duplicate

This commit is contained in:
HighImKevin
2026-10-18 13:34:52 -04:00
parent ebfc62cc77
commit 1ff8c593f4
2 changed files with 812 additions and 97 deletions
+150 -66
View File
@@ -4930,48 +4930,42 @@ window.KefinTweaksConfig = ${JSON.stringify(configToBackup, null, 2)};`;
}); });
confirmBtn?.addEventListener('click', async () => { confirmBtn?.addEventListener('click', async () => {
const selectedIndices = Array.from(dialog.querySelectorAll('.import-community-check:checked')) const selectedIndices = Array.from(dialog.querySelectorAll('.import-community-check:checked:not(:disabled)'))
.map(c => parseInt(c.dataset.index, 10)); .map(c => parseInt(c.dataset.index, 10));
if (selectedIndices.length === 0) { if (selectedIndices.length === 0) {
showToast('Please select at least one section to import.'); showToast('Please select at least one new section to import.');
return; return;
} }
if (!currentConfig.CUSTOM_SECTION_GROUPS) currentConfig.CUSTOM_SECTION_GROUPS = [];
const selectedSections = selectedIndices.map(i => { const selectedSections = selectedIndices.map(i => {
const section = JSON.parse(JSON.stringify(collection.sections[i])); const section = JSON.parse(JSON.stringify(collection.sections[i]));
return convertToHomeScreenSection(section); return convertToHomeScreenSection(section);
}); });
const importedGroup = { const { importedCount, skippedCount } = importCommunitySectionsIntoConfig(
name: collection.name || 'Imported Collection', collection,
author: collection.author, selectedSections
description: collection.description, );
sections: selectedSections.map(section => {
if (section.id && sectionIdExists(currentConfig, section.id)) {
section.id = `${section.id}_community_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
}
return section;
})
};
currentConfig.CUSTOM_SECTION_GROUPS.push(importedGroup); if (importedCount === 0) {
const importedCount = selectedSections.length; showToast(skippedCount > 0
? 'Selected sections are already imported.'
: 'Nothing new to import.');
return;
}
if (importedCount > 0) { confirmBtn.disabled = true;
confirmBtn.disabled = true; try {
try { await saveConfig(currentConfig);
await saveConfig(currentConfig); window.ModalSystem.close(COMMUNITY_IMPORT_MODAL_ID);
window.ModalSystem.close(COMMUNITY_IMPORT_MODAL_ID); const skipNote = skippedCount > 0 ? ` (${skippedCount} already present)` : '';
showToast(`Successfully imported ${importedCount} sections. Refreshing...`); showToast(`Successfully imported ${importedCount} section${importedCount !== 1 ? 's' : ''}${skipNote}. Refreshing...`);
refreshMainModal(); refreshMainModal();
} catch (e) { } catch (e) {
ERR('Error importing community sections:', e); ERR('Error importing community sections:', e);
showToast('Error importing sections'); showToast('Error importing sections');
confirmBtn.disabled = false; confirmBtn.disabled = false;
}
} }
}); });
} }
@@ -4992,19 +4986,28 @@ window.KefinTweaksConfig = ${JSON.stringify(configToBackup, null, 2)};`;
return; return;
} }
const existingGroup = findCommunityImportGroup(currentConfig.CUSTOM_SECTION_GROUPS || [], collection);
let availableCount = 0;
sectionsList.innerHTML = ` sectionsList.innerHTML = `
<div style="background: rgba(0,0,0,0.2); border-radius: 4px; padding: 0.5em;"> <div style="background: rgba(0,0,0,0.2); border-radius: 4px; padding: 0.5em;">
${collection.sections.map((s, i) => { ${collection.sections.map((s, i) => {
const name = escapeHtml(s.name || 'Unnamed'); const name = escapeHtml(s.name || 'Unnamed');
const alreadyImported = isCommunitySectionInGroup(existingGroup, s);
if (!alreadyImported) availableCount++;
const desc = s.description const desc = s.description
? `<div class="listItemBodyText secondary" style="font-size: 0.85em; margin-top: 0.25em;">${escapeHtml(String(s.description))}</div>` ? `<div class="listItemBodyText secondary" style="font-size: 0.85em; margin-top: 0.25em;">${escapeHtml(String(s.description))}</div>`
: ''; : '';
const importedNote = alreadyImported
? `<div class="listItemBodyText secondary" style="font-size: 0.8em; margin-top: 0.25em; opacity: 0.7;">Already imported</div>`
: '';
return ` return `
<label class="checkboxContainer" style="display: flex; align-items: flex-start; padding: 0.5em; margin-bottom: 0.25em;"> <label class="checkboxContainer" style="display: flex; align-items: flex-start; padding: 0.5em; margin-bottom: 0.25em; ${alreadyImported ? 'opacity: 0.55;' : ''}">
<input type="checkbox" class="import-community-check" data-index="${i}" checked style="margin-top: 0.2em;"> <input type="checkbox" class="import-community-check" data-index="${i}" ${alreadyImported ? 'disabled' : 'checked'} style="margin-top: 0.2em;">
<div style="margin-left: 0.5em; flex: 1; min-width: 0;"> <div style="margin-left: 0.5em; flex: 1; min-width: 0;">
<span class="listItemBodyText">${name}</span> <span class="listItemBodyText">${name}</span>
${desc} ${desc}
${importedNote}
</div> </div>
</label> </label>
`; `;
@@ -5014,7 +5017,11 @@ window.KefinTweaksConfig = ${JSON.stringify(configToBackup, null, 2)};`;
if (sectionsContainer) sectionsContainer.style.display = 'block'; if (sectionsContainer) sectionsContainer.style.display = 'block';
if (sectionsEmpty) sectionsEmpty.style.display = 'none'; if (sectionsEmpty) sectionsEmpty.style.display = 'none';
if (confirmBtn) confirmBtn.style.display = 'inline-block'; if (confirmBtn) {
confirmBtn.style.display = 'inline-block';
confirmBtn.disabled = availableCount === 0;
confirmBtn.textContent = availableCount === 0 ? 'Already Imported' : 'Import Selected';
}
} }
/** /**
@@ -5147,6 +5154,82 @@ window.KefinTweaksConfig = ${JSON.stringify(configToBackup, null, 2)};`;
return allIds.includes(sectionId); return allIds.includes(sectionId);
} }
/**
* Find an existing custom group matching a community collection (name + author).
*/
function findCommunityImportGroup(groups, collection) {
const name = collection?.name || 'Imported Collection';
const author = collection?.author;
return (groups || []).find((g) => {
if (g.name !== name) return false;
if (author) return g.author === author;
return !g.author;
}) || null;
}
/**
* Whether a community section is already present in a target group.
* Matches exact id, prior community re-import ids, or same section name.
*/
function isCommunitySectionInGroup(group, section) {
if (!group?.sections?.length || !section) return false;
const id = section.id;
const name = section.name;
return group.sections.some((s) => {
if (id && s.id === id) return true;
if (id && s.id && String(s.id).startsWith(`${id}_community_`)) return true;
if (name && s.name === name) return true;
return false;
});
}
/**
* Import selected community sections into CUSTOM_SECTION_GROUPS,
* merging into an existing name+author group and skipping duplicates.
* @returns {{ importedCount: number, skippedCount: number, group: Object|null }}
*/
function importCommunitySectionsIntoConfig(collection, selectedSections) {
if (!currentConfig.CUSTOM_SECTION_GROUPS) currentConfig.CUSTOM_SECTION_GROUPS = [];
const existingGroup = findCommunityImportGroup(currentConfig.CUSTOM_SECTION_GROUPS, collection);
const toAdd = [];
let skippedCount = 0;
selectedSections.forEach((section) => {
if (isCommunitySectionInGroup(existingGroup, section)) {
skippedCount++;
return;
}
if (section.id && sectionIdExists(currentConfig, section.id)) {
section.id = `${section.id}_community_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
}
toAdd.push(section);
});
if (toAdd.length === 0) {
return { importedCount: 0, skippedCount, group: existingGroup };
}
let targetGroup = existingGroup;
if (!targetGroup) {
targetGroup = {
name: collection.name || 'Imported Collection',
author: collection.author,
description: collection.description,
sections: []
};
currentConfig.CUSTOM_SECTION_GROUPS.push(targetGroup);
} else if (collection.description && !targetGroup.description) {
targetGroup.description = collection.description;
}
if (!targetGroup.sections) targetGroup.sections = [];
targetGroup.sections.push(...toAdd);
targetGroup.sections.sort((a, b) => (a.order || 0) - (b.order || 0));
return { importedCount: toAdd.length, skippedCount, group: targetGroup };
}
/** /**
* Show community import dialog * Show community import dialog
*/ */
@@ -5229,15 +5312,24 @@ window.KefinTweaksConfig = ${JSON.stringify(configToBackup, null, 2)};`;
// Render sections list // Render sections list
let html = ''; let html = '';
let availableCount = 0;
if (selectedCollection.sections && selectedCollection.sections.length > 0) { if (selectedCollection.sections && selectedCollection.sections.length > 0) {
const existingGroup = findCommunityImportGroup(
currentConfig.CUSTOM_SECTION_GROUPS || [],
selectedCollection
);
html += ` html += `
<div style="background: rgba(0,0,0,0.2); border-radius: 4px; padding: 0.5em;"> <div style="background: rgba(0,0,0,0.2); border-radius: 4px; padding: 0.5em;">
${selectedCollection.sections.map((s, i) => ` ${selectedCollection.sections.map((s, i) => {
<label class="checkboxContainer" style="display: flex; align-items: center; padding: 0.5em;"> const alreadyImported = isCommunitySectionInGroup(existingGroup, s);
<input type="checkbox" class="import-community-check" data-index="${i}" checked> if (!alreadyImported) availableCount++;
<span class="listItemBodyText" style="margin-left: 0.5em;">${s.name || 'Unnamed'}</span> return `
<label class="checkboxContainer" style="display: flex; align-items: center; padding: 0.5em; ${alreadyImported ? 'opacity: 0.55;' : ''}">
<input type="checkbox" class="import-community-check" data-index="${i}" ${alreadyImported ? 'disabled' : 'checked'}>
<span class="listItemBodyText" style="margin-left: 0.5em;">${escapeHtml(s.name || 'Unnamed')}${alreadyImported ? ' (already imported)' : ''}</span>
</label> </label>
`).join('')} `;
}).join('')}
</div>`; </div>`;
} else { } else {
html = '<div class="listItemBodyText secondary">No sections found in this collection.</div>'; html = '<div class="listItemBodyText secondary">No sections found in this collection.</div>';
@@ -5246,51 +5338,43 @@ window.KefinTweaksConfig = ${JSON.stringify(configToBackup, null, 2)};`;
selectionList.innerHTML = html; selectionList.innerHTML = html;
selectionContainer.style.display = 'block'; selectionContainer.style.display = 'block';
confirmBtn.style.display = 'inline-block'; confirmBtn.style.display = 'inline-block';
confirmBtn.disabled = availableCount === 0;
confirmBtn.textContent = availableCount === 0 ? 'Already Imported' : 'Import Selected';
}); });
confirmBtn.addEventListener('click', async () => { confirmBtn.addEventListener('click', async () => {
if (!selectedCollection) return; if (!selectedCollection) return;
const selectedIndices = Array.from(content.querySelectorAll('.import-community-check:checked')).map(c => parseInt(c.dataset.index)); const selectedIndices = Array.from(content.querySelectorAll('.import-community-check:checked:not(:disabled)'))
.map(c => parseInt(c.dataset.index));
if (selectedIndices.length === 0) { if (selectedIndices.length === 0) {
showToast('Please select at least one section to import.'); showToast('Please select at least one new section to import.');
return; return;
} }
// Import logic - import entire groups to CUSTOM_SECTION_GROUPS
if (!currentConfig.CUSTOM_SECTION_GROUPS) currentConfig.CUSTOM_SECTION_GROUPS = [];
const selectedSections = selectedIndices.map(i => { const selectedSections = selectedIndices.map(i => {
const section = JSON.parse(JSON.stringify(selectedCollection.sections[i])); // Deep copy const section = JSON.parse(JSON.stringify(selectedCollection.sections[i]));
return convertToHomeScreenSection(section); return convertToHomeScreenSection(section);
}); });
// Create a group from the collection const { importedCount, skippedCount } = importCommunitySectionsIntoConfig(
const importedGroup = { selectedCollection,
name: selectedCollection.name || 'Imported Collection', selectedSections
author: selectedCollection.author, );
description: selectedCollection.description,
sections: selectedSections.map(section => {
// Check for ID collision - only regenerate if needed
if (section.id && sectionIdExists(currentConfig, section.id)) {
section.id = `${section.id}_community_${Date.now()}_${Math.floor(Math.random()*1000)}`;
}
return section;
})
};
currentConfig.CUSTOM_SECTION_GROUPS.push(importedGroup); if (importedCount === 0) {
const importedCount = selectedSections.length; showToast(skippedCount > 0
? 'Selected sections are already imported.'
if (importedCount > 0) { : 'Nothing new to import.');
await saveConfig(currentConfig); return;
showToast(`Successfully imported ${importedCount} sections. Refreshing...`);
refreshMainModal();
window.ModalSystem.close('kefinCommunityImportModal');
} else {
window.ModalSystem.close('kefinCommunityImportModal');
} }
await saveConfig(currentConfig);
const skipNote = skippedCount > 0 ? ` (${skippedCount} already present)` : '';
showToast(`Successfully imported ${importedCount} section${importedCount !== 1 ? 's' : ''}${skipNote}. Refreshing...`);
refreshMainModal();
window.ModalSystem.close('kefinCommunityImportModal');
}); });
} }
}); });
+660 -29
View File
@@ -86,67 +86,665 @@
] ]
}, },
{ {
"name": "Genre Sections", "name": "Multi Query Examples",
"author": "HighImKevin", "author": "HighImKevin",
"description": "Custom section examples that use the multi-query picker.",
"sections": [ "sections": [
{ {
"name": "Popular Movie Genres", "id": "custom-1791197522382",
"name": "Our Top Movie Choices",
"enabled": true, "enabled": true,
"order": 22, "order": 100,
"discoveryEnabled": false, "category": "none",
"renderMode": "Normal", "cardFormat": "Poster",
"cardFormat": "Thumb", "ttl": 86400000,
"queries": [ "queries": [
{ {
"path": "/Genres", "name": "Best of Action",
"queryOptions": { "queryOptions": {
"IncludeItemTypes": [ "IncludeItemTypes": [
"Movie" "Movie"
], ],
"SortBy": "ChildCount", "Genres": "Action",
"SortOrder": "Descending", "SortBy": "Random",
"Limit": 20 "SortOrder": "Ascending",
"Filters": "IsUnplayed",
"Limit": 50,
"MinCommunityRating": 7.5
}
},
{
"name": "Best of Comedy",
"queryOptions": {
"IncludeItemTypes": [
"Movie"
],
"Genres": "Comedy",
"SortBy": "Random",
"SortOrder": "Ascending",
"Filters": "IsUnplayed",
"Limit": 50,
"MinCommunityRating": 7.5
}
},
{
"name": "Best of Drama",
"queryOptions": {
"IncludeItemTypes": [
"Movie"
],
"Genres": "Drama",
"SortBy": "Random",
"SortOrder": "Ascending",
"Filters": "IsUnplayed",
"Limit": 50,
"MinCommunityRating": 7.5
}
},
{
"name": "Best of Horror",
"queryOptions": {
"IncludeItemTypes": [
"Movie"
],
"Genres": "Horror",
"SortBy": "Random",
"SortOrder": "Ascending",
"Filters": "IsUnplayed",
"Limit": 50,
"MinCommunityRating": 7.5
}
},
{
"name": "Best of Science Fiction",
"queryOptions": {
"IncludeItemTypes": [
"Movie"
],
"Genres": "Science Fiction",
"SortBy": "Random",
"SortOrder": "Ascending",
"Filters": "IsUnplayed",
"Limit": 50,
"MinCommunityRating": 7.5
}
},
{
"name": "Best of Romance",
"queryOptions": {
"IncludeItemTypes": [
"Movie"
],
"Genres": "Romance",
"SortBy": "Random",
"SortOrder": "Ascending",
"Filters": "IsUnplayed",
"Limit": 50,
"MinCommunityRating": 7.5
} }
} }
], ],
"flattenSeries": false, "userConfigurable": true,
"id": "custom-1768961093582", "renderMode": "Normal",
"ttl": 86400000, "discoveryEnabled": false,
"type": "custom" "type": "home",
"useRandomQuery": true,
"useMultiQueryPicker": true,
"isCustom": true,
"description": "Our top offerings by genre",
"useGaplessCards": false,
"useParentCard": false,
"itemLimit": 16,
"useQueryNamesForSection": false
}, },
{ {
"name": "Popular TV Genres", "id": "custom-1791256768605",
"enabled": true, "name": "Movies by the Decade",
"enabled": false,
"order": 100, "order": 100,
"discoveryEnabled": false, "category": "none",
"renderMode": "Normal", "cardFormat": "Poster",
"cardFormat": "Thumb", "ttl": 86400000,
"queries": [ "queries": [
{ {
"path": "/Genres", "name": "50s Movies",
"queryOptions": {
"IncludeItemTypes": [
"Movie"
],
"SortBy": "Random",
"SortOrder": "Ascending",
"Filters": "IsUnplayed",
"Limit": 20,
"Years": [
"1950",
"1951",
"1952",
"1953",
"1954",
"1955",
"1956",
"1957",
"1958",
"1959"
]
}
},
{
"name": "60s Movies",
"queryOptions": {
"IncludeItemTypes": [
"Movie"
],
"SortBy": "Random",
"SortOrder": "Ascending",
"Filters": "IsUnplayed",
"Limit": 20,
"Years": [
"1960",
"1961",
"1962",
"1963",
"1964",
"1965",
"1966",
"1967",
"1968",
"1969"
]
}
},
{
"name": "70s Movies",
"queryOptions": {
"IncludeItemTypes": [
"Movie"
],
"SortBy": "Random",
"SortOrder": "Ascending",
"Filters": "IsUnplayed",
"Limit": 20,
"Years": [
"1970",
"1971",
"1972",
"1973",
"1974",
"1975",
"1976",
"1977",
"1978",
"1979"
]
}
},
{
"name": "80s Movies",
"queryOptions": {
"IncludeItemTypes": [
"Movie"
],
"SortBy": "Random",
"SortOrder": "Ascending",
"Filters": "IsUnplayed",
"Limit": 20,
"Years": [
"1980",
"1981",
"1982",
"1983",
"1984",
"1985",
"1986",
"1987",
"1988",
"1989"
]
}
},
{
"name": "90s Movies",
"queryOptions": {
"IncludeItemTypes": [
"Movie"
],
"SortBy": "Random",
"SortOrder": "Ascending",
"Filters": "IsUnplayed",
"Limit": 20,
"Years": [
"1990",
"1991",
"1992",
"1993",
"1994",
"1995",
"1996",
"1997",
"1998",
"1999"
]
}
},
{
"name": "2000s Movies",
"queryOptions": {
"IncludeItemTypes": [
"Movie"
],
"SortBy": "Random",
"SortOrder": "Ascending",
"Filters": "IsUnplayed",
"Limit": 20,
"Years": [
"2000",
"2001",
"2002",
"2003",
"2004",
"2005",
"2006",
"2007",
"2008",
"2009"
]
}
},
{
"name": "2010s Movies",
"queryOptions": {
"IncludeItemTypes": [
"Movie"
],
"SortBy": "Random",
"SortOrder": "Ascending",
"Filters": "IsUnplayed",
"Limit": 20,
"Years": [
"2010",
"2011",
"2012",
"2013",
"2014",
"2015",
"2016",
"2017",
"2018",
"2019"
]
}
},
{
"name": "2020s Movies",
"queryOptions": {
"IncludeItemTypes": [
"Movie"
],
"SortBy": "Random",
"SortOrder": "Ascending",
"Filters": "IsUnplayed",
"Limit": 20,
"Years": [
"2020",
"2021",
"2022",
"2023",
"2024",
"2025",
"2026",
"2027",
"2028",
"2029"
]
}
}
],
"caption": "Browse by decade",
"userConfigurable": true,
"renderMode": "Normal",
"discoveryEnabled": false,
"type": "home",
"useRandomQuery": true,
"useMultiQueryPicker": true,
"multiQueryPickerLabel": "Pick a Decade",
"useQueryNamesForSection": true,
"isCustom": true,
"description": "Find something to watch from your favorite decade",
"useGaplessCards": false,
"useParentCard": false
},
{
"id": "custom-1791256768606",
"name": "Our Top Series Choices",
"enabled": true,
"order": 100,
"category": "none",
"cardFormat": "Poster",
"ttl": 86400000,
"queries": [
{
"name": "Best of Action",
"queryOptions": { "queryOptions": {
"IncludeItemTypes": [ "IncludeItemTypes": [
"Series" "Series"
], ],
"SortBy": "ChildCount", "Genres": "Action",
"SortOrder": "Descending", "SortBy": "Random",
"Limit": 20 "SortOrder": "Ascending",
"Filters": "IsUnplayed",
"Limit": 50,
"MinCommunityRating": 7.5
}
},
{
"name": "Best of Comedy",
"queryOptions": {
"IncludeItemTypes": [
"Series"
],
"Genres": "Comedy",
"SortBy": "Random",
"SortOrder": "Ascending",
"Filters": "IsUnplayed",
"Limit": 50,
"MinCommunityRating": 7.5
}
},
{
"name": "Best of Drama",
"queryOptions": {
"IncludeItemTypes": [
"Series"
],
"Genres": "Drama",
"SortBy": "Random",
"SortOrder": "Ascending",
"Filters": "IsUnplayed",
"Limit": 50,
"MinCommunityRating": 7.5
}
},
{
"name": "Best of Horror",
"queryOptions": {
"IncludeItemTypes": [
"Series"
],
"Genres": "Horror",
"SortBy": "Random",
"SortOrder": "Ascending",
"Filters": "IsUnplayed",
"Limit": 50,
"MinCommunityRating": 7.5
}
},
{
"name": "Best of Science Fiction",
"queryOptions": {
"IncludeItemTypes": [
"Series"
],
"Genres": "Science Fiction",
"SortBy": "Random",
"SortOrder": "Ascending",
"Filters": "IsUnplayed",
"Limit": 50,
"MinCommunityRating": 7.5
}
},
{
"name": "Best of Romance",
"queryOptions": {
"IncludeItemTypes": [
"Series"
],
"Genres": "Romance",
"SortBy": "Random",
"SortOrder": "Ascending",
"Filters": "IsUnplayed",
"Limit": 50,
"MinCommunityRating": 7.5
} }
} }
], ],
"flattenSeries": false, "userConfigurable": true,
"id": "custom-1770808132827", "renderMode": "Normal",
"discoveryEnabled": false,
"type": "home",
"useRandomQuery": true,
"useMultiQueryPicker": true,
"isCustom": true,
"description": "Our top series offerings by genre",
"useGaplessCards": false,
"useParentCard": false,
"itemLimit": 16,
"useQueryNamesForSection": false
},
{
"id": "custom-1791256768607",
"name": "Series by the Decade",
"enabled": false,
"order": 100,
"category": "none",
"cardFormat": "Poster",
"ttl": 86400000, "ttl": 86400000,
"type": "custom" "queries": [
{
"name": "50s Series",
"queryOptions": {
"IncludeItemTypes": [
"Series"
],
"SortBy": "Random",
"SortOrder": "Ascending",
"Filters": "IsUnplayed",
"Limit": 20,
"Years": [
"1950",
"1951",
"1952",
"1953",
"1954",
"1955",
"1956",
"1957",
"1958",
"1959"
]
}
},
{
"name": "60s Series",
"queryOptions": {
"IncludeItemTypes": [
"Series"
],
"SortBy": "Random",
"SortOrder": "Ascending",
"Filters": "IsUnplayed",
"Limit": 20,
"Years": [
"1960",
"1961",
"1962",
"1963",
"1964",
"1965",
"1966",
"1967",
"1968",
"1969"
]
}
},
{
"name": "70s Series",
"queryOptions": {
"IncludeItemTypes": [
"Series"
],
"SortBy": "Random",
"SortOrder": "Ascending",
"Filters": "IsUnplayed",
"Limit": 20,
"Years": [
"1970",
"1971",
"1972",
"1973",
"1974",
"1975",
"1976",
"1977",
"1978",
"1979"
]
}
},
{
"name": "80s Series",
"queryOptions": {
"IncludeItemTypes": [
"Series"
],
"SortBy": "Random",
"SortOrder": "Ascending",
"Filters": "IsUnplayed",
"Limit": 20,
"Years": [
"1980",
"1981",
"1982",
"1983",
"1984",
"1985",
"1986",
"1987",
"1988",
"1989"
]
}
},
{
"name": "90s Series",
"queryOptions": {
"IncludeItemTypes": [
"Series"
],
"SortBy": "Random",
"SortOrder": "Ascending",
"Filters": "IsUnplayed",
"Limit": 20,
"Years": [
"1990",
"1991",
"1992",
"1993",
"1994",
"1995",
"1996",
"1997",
"1998",
"1999"
]
}
},
{
"name": "2000s Series",
"queryOptions": {
"IncludeItemTypes": [
"Series"
],
"SortBy": "Random",
"SortOrder": "Ascending",
"Filters": "IsUnplayed",
"Limit": 20,
"Years": [
"2000",
"2001",
"2002",
"2003",
"2004",
"2005",
"2006",
"2007",
"2008",
"2009"
]
}
},
{
"name": "2010s Series",
"queryOptions": {
"IncludeItemTypes": [
"Series"
],
"SortBy": "Random",
"SortOrder": "Ascending",
"Filters": "IsUnplayed",
"Limit": 20,
"Years": [
"2010",
"2011",
"2012",
"2013",
"2014",
"2015",
"2016",
"2017",
"2018",
"2019"
]
}
},
{
"name": "2020s Series",
"queryOptions": {
"IncludeItemTypes": [
"Series"
],
"SortBy": "Random",
"SortOrder": "Ascending",
"Filters": "IsUnplayed",
"Limit": 20,
"Years": [
"2020",
"2021",
"2022",
"2023",
"2024",
"2025",
"2026",
"2027",
"2028",
"2029"
]
}
}
],
"caption": "Browse by decade",
"userConfigurable": true,
"renderMode": "Normal",
"discoveryEnabled": false,
"type": "home",
"useRandomQuery": true,
"useMultiQueryPicker": true,
"multiQueryPickerLabel": "Pick a Decade",
"useQueryNamesForSection": true,
"isCustom": true,
"description": "Find a series from your favorite decade",
"useGaplessCards": false,
"useParentCard": false
} }
] ]
}, },
{ {
"name": "Prolific People [Discovery]", "name": "Prolific People [Discovery]",
"author": "HighImKevin", "author": "HighImKevin",
"description": "Discovery sections featuring prolific movie people, plus top series actors.",
"sections": [ "sections": [
{ {
"name": "Prolific Writers", "name": "Prolific Movie Writers",
"enabled": true, "enabled": true,
"order": 100, "order": 100,
"discoveryEnabled": true, "discoveryEnabled": true,
@@ -155,6 +753,9 @@
"queries": [ "queries": [
{ {
"dataSource": "PeopleCache.getTopWriters", "dataSource": "PeopleCache.getTopWriters",
"dataSourceOptions": {
"ItemType": "Movie"
},
"queryOptions": { "queryOptions": {
"SortBy": "Random", "SortBy": "Random",
"SortOrder": "Ascending", "SortOrder": "Ascending",
@@ -167,7 +768,7 @@
"type": "custom" "type": "custom"
}, },
{ {
"name": "Prolific Directors", "name": "Prolific Movie Directors",
"enabled": true, "enabled": true,
"order": 100, "order": 100,
"discoveryEnabled": true, "discoveryEnabled": true,
@@ -176,6 +777,9 @@
"queries": [ "queries": [
{ {
"dataSource": "PeopleCache.getTopDirectors", "dataSource": "PeopleCache.getTopDirectors",
"dataSourceOptions": {
"ItemType": "Movie"
},
"queryOptions": { "queryOptions": {
"SortBy": "Random", "SortBy": "Random",
"SortOrder": "Ascending", "SortOrder": "Ascending",
@@ -188,7 +792,7 @@
"type": "custom" "type": "custom"
}, },
{ {
"name": "Prolific Actors", "name": "Prolific Movie Actors",
"enabled": true, "enabled": true,
"order": 250, "order": 250,
"discoveryEnabled": true, "discoveryEnabled": true,
@@ -197,6 +801,9 @@
"queries": [ "queries": [
{ {
"dataSource": "PeopleCache.getTopActors", "dataSource": "PeopleCache.getTopActors",
"dataSourceOptions": {
"ItemType": "Movie"
},
"queryOptions": { "queryOptions": {
"SortBy": "Random", "SortBy": "Random",
"SortOrder": "Ascending", "SortOrder": "Ascending",
@@ -207,6 +814,30 @@
"flattenSeries": false, "flattenSeries": false,
"id": "custom-1768973523242", "id": "custom-1768973523242",
"type": "custom" "type": "custom"
},
{
"name": "Prolific Series Actors",
"enabled": true,
"order": 250,
"discoveryEnabled": true,
"renderMode": "Normal",
"cardFormat": "Poster",
"queries": [
{
"dataSource": "PeopleCache.getTopActors",
"dataSourceOptions": {
"ItemType": "Series"
},
"queryOptions": {
"SortBy": "Random",
"SortOrder": "Ascending",
"Limit": 20
}
}
],
"flattenSeries": false,
"id": "custom-1791300000001",
"type": "custom"
} }
] ]
} }