feat(sets): added filter on sets page to show duplicate sets. default is shown. can be hidden using env var. works with consolidated sets too.

This commit is contained in:
2025-10-03 09:13:15 +02:00
parent 4b653ac270
commit a3d08d8cf6
12 changed files with 196 additions and 7 deletions

View File

@@ -180,7 +180,12 @@ class BrickGridFilter {
}
// If we passed all filters, we need to display it
current.parentElement.classList.remove("d-none");
// But also check if it's hidden by duplicate filter
if (!current.parentElement.classList.contains("duplicate-filter-hidden")) {
current.parentElement.classList.remove("d-none");
} else {
current.parentElement.classList.add("d-none");
}
});
}
}

View File

@@ -19,6 +19,9 @@ document.addEventListener("DOMContentLoaded", () => {
const searchInput = document.getElementById('grid-search');
const searchClear = document.getElementById('grid-search-clear');
// Initialize duplicate filter functionality
initializeDuplicateFilter();
if (searchInput && searchClear) {
if (isPaginationMode()) {
// PAGINATION MODE - Server-side search
@@ -559,4 +562,146 @@ function removeSetGrouping() {
groupContainers.forEach(container => {
container.remove();
});
}
// Initialize duplicate/consolidated filter functionality
function initializeDuplicateFilter() {
const duplicateFilterButton = document.getElementById('duplicate-filter-toggle');
if (!duplicateFilterButton) return;
// Check if the filter should be active from URL parameters
const urlParams = new URLSearchParams(window.location.search);
const isDuplicateFilterActive = urlParams.get('duplicate') === 'true';
// Set initial button state
if (isDuplicateFilterActive) {
duplicateFilterButton.classList.remove('btn-outline-secondary');
duplicateFilterButton.classList.add('btn-secondary');
}
duplicateFilterButton.addEventListener('click', () => {
const isCurrentlyActive = duplicateFilterButton.classList.contains('btn-secondary');
const newState = !isCurrentlyActive;
// Update button appearance
if (newState) {
duplicateFilterButton.classList.remove('btn-outline-secondary');
duplicateFilterButton.classList.add('btn-secondary');
} else {
duplicateFilterButton.classList.remove('btn-secondary');
duplicateFilterButton.classList.add('btn-outline-secondary');
}
if (isPaginationMode()) {
// SERVER-SIDE MODE - Update URL parameter
performDuplicateFilterServer(newState);
} else {
// CLIENT-SIDE MODE - Apply filtering directly
applyDuplicateFilter(newState);
}
});
}
// Server-side duplicate filter
function performDuplicateFilterServer(showOnlyDuplicates) {
const currentUrl = new URL(window.location);
if (showOnlyDuplicates) {
currentUrl.searchParams.set('duplicate', 'true');
} else {
currentUrl.searchParams.delete('duplicate');
}
// Reset to page 1 when filtering
currentUrl.searchParams.set('page', '1');
window.location.href = currentUrl.toString();
}
// Apply duplicate/consolidated filter
function applyDuplicateFilter(showOnlyDuplicates) {
// Get the grid container and all column containers (not just the cards)
const gridContainer = document.getElementById('grid');
if (!gridContainer) {
console.warn('Grid container not found');
return;
}
// Try multiple selectors to find column containers
let columnContainers = gridContainer.querySelectorAll('.col-md-6');
if (columnContainers.length === 0) {
columnContainers = gridContainer.querySelectorAll('[class*="col-"]');
}
if (!showOnlyDuplicates) {
// Show all column containers by removing the duplicate-filter-hidden class
columnContainers.forEach(col => {
col.classList.remove('duplicate-filter-hidden');
});
// Trigger the existing grid filter to refresh
triggerGridRefresh();
return;
}
// Check if we're in consolidated mode by looking for data-instance-count
const consolidatedMode = document.querySelector('[data-instance-count]') !== null;
if (consolidatedMode) {
// CONSOLIDATED MODE: Show only sets with instance count > 1
columnContainers.forEach(col => {
const card = col.querySelector('[data-set-id]');
if (card) {
const instanceCount = parseInt(card.dataset.instanceCount || '1');
if (instanceCount > 1) {
col.classList.remove('duplicate-filter-hidden');
} else {
col.classList.add('duplicate-filter-hidden');
}
}
});
} else {
// NON-CONSOLIDATED MODE: Show only sets that appear multiple times
const setByCounts = {};
// Count occurrences of each set
columnContainers.forEach(col => {
const card = col.querySelector('[data-set-id]');
if (card) {
const rebrickableId = card.dataset.rebrickableId;
if (rebrickableId) {
setByCounts[rebrickableId] = (setByCounts[rebrickableId] || 0) + 1;
}
}
});
// Show/hide based on count
columnContainers.forEach(col => {
const card = col.querySelector('[data-set-id]');
if (card) {
const rebrickableId = card.dataset.rebrickableId;
if (rebrickableId && setByCounts[rebrickableId] > 1) {
col.classList.remove('duplicate-filter-hidden');
} else {
col.classList.add('duplicate-filter-hidden');
}
}
});
}
// Trigger the existing grid filter to refresh and respect our duplicate filter
triggerGridRefresh();
}
// Helper function to trigger grid filter refresh
function triggerGridRefresh() {
// Check if we have a grid instance with filter capability
if (window.gridInstances) {
const gridElement = document.getElementById('grid');
if (gridElement && window.gridInstances[gridElement.id]) {
const gridInstance = window.gridInstances[gridElement.id];
if (gridInstance.filter) {
// Trigger the existing filter to refresh
gridInstance.filter.filter();
}
}
}
}

View File

@@ -178,4 +178,5 @@
border-radius: 2px;
opacity: 0.8;
pointer-events: none;
}
}/* Duplicate filter support */
.duplicate-filter-hidden { display: none !important; }