|
|
|
|
|
let allProjects = []; |
|
|
let filteredProjects = []; |
|
|
let currentPlatformFilter = 'all'; |
|
|
let currentUsecaseFilter = 'all'; |
|
|
let currentSearchQuery = ''; |
|
|
let currentSort = 'name'; |
|
|
|
|
|
|
|
|
async function loadProjects() { |
|
|
try { |
|
|
const response = await fetch('projects.json'); |
|
|
allProjects = await response.json(); |
|
|
filteredProjects = [...allProjects]; |
|
|
renderProjects(); |
|
|
updateProjectCount(); |
|
|
} catch (error) { |
|
|
console.error('Error loading projects:', error); |
|
|
document.getElementById('projects-grid').innerHTML = ` |
|
|
<div class="no-results"> |
|
|
<h3>Error loading projects</h3> |
|
|
<p>Unable to load the projects data. Please try refreshing the page.</p> |
|
|
</div> |
|
|
`; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function applyFilters() { |
|
|
filteredProjects = allProjects.filter(project => { |
|
|
|
|
|
const platformMatch = currentPlatformFilter === 'all' || |
|
|
project.platforms.includes(currentPlatformFilter); |
|
|
|
|
|
|
|
|
const usecaseMatch = currentUsecaseFilter === 'all' || |
|
|
project.usecases.includes(currentUsecaseFilter); |
|
|
|
|
|
|
|
|
const searchMatch = currentSearchQuery === '' || |
|
|
project.name.toLowerCase().includes(currentSearchQuery.toLowerCase()) || |
|
|
project.description.toLowerCase().includes(currentSearchQuery.toLowerCase()); |
|
|
|
|
|
return platformMatch && usecaseMatch && searchMatch; |
|
|
}); |
|
|
|
|
|
sortProjects(); |
|
|
renderProjects(); |
|
|
updateProjectCount(); |
|
|
} |
|
|
|
|
|
|
|
|
function sortProjects() { |
|
|
switch (currentSort) { |
|
|
case 'name': |
|
|
filteredProjects.sort((a, b) => a.name.localeCompare(b.name)); |
|
|
break; |
|
|
case 'platform': |
|
|
filteredProjects.sort((a, b) => { |
|
|
const platformA = a.platforms[0] || ''; |
|
|
const platformB = b.platforms[0] || ''; |
|
|
return platformA.localeCompare(platformB); |
|
|
}); |
|
|
break; |
|
|
case 'usecase': |
|
|
filteredProjects.sort((a, b) => { |
|
|
const usecaseA = a.usecases[0] || ''; |
|
|
const usecaseB = b.usecases[0] || ''; |
|
|
return usecaseA.localeCompare(usecaseB); |
|
|
}); |
|
|
break; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function renderProjects() { |
|
|
const grid = document.getElementById('projects-grid'); |
|
|
|
|
|
if (filteredProjects.length === 0) { |
|
|
grid.innerHTML = ` |
|
|
<div class="no-results"> |
|
|
<h3>No projects found</h3> |
|
|
<p>Try adjusting your filters or search query.</p> |
|
|
</div> |
|
|
`; |
|
|
return; |
|
|
} |
|
|
|
|
|
grid.innerHTML = filteredProjects.map(project => createProjectCard(project)).join(''); |
|
|
} |
|
|
|
|
|
|
|
|
function createProjectCard(project) { |
|
|
const platformTags = project.platforms |
|
|
.map(p => `<span class="meta-tag platform-tag">${formatTag(p)}</span>`) |
|
|
.join(''); |
|
|
|
|
|
const usecaseTags = project.usecases |
|
|
.map(u => `<span class="meta-tag usecase-tag">${formatTag(u)}</span>`) |
|
|
.join(''); |
|
|
|
|
|
const starBadge = project.github_repo |
|
|
? `<img src="https://img.shields.io/github/stars/${project.github_repo}?style=flat-square" alt="GitHub stars" class="github-stars-badge">` |
|
|
: ''; |
|
|
|
|
|
return ` |
|
|
<div class="project-card"> |
|
|
<h3> |
|
|
<a href="${project.url}" target="_blank" rel="noopener">${escapeHtml(project.name)}</a> |
|
|
${starBadge} |
|
|
</h3> |
|
|
<p class="project-description">${escapeHtml(project.description)}</p> |
|
|
<div class="project-meta"> |
|
|
${platformTags} |
|
|
${usecaseTags} |
|
|
</div> |
|
|
</div> |
|
|
`; |
|
|
} |
|
|
|
|
|
|
|
|
function formatTag(tag) { |
|
|
return tag |
|
|
.split('-') |
|
|
.map(word => word.charAt(0).toUpperCase() + word.slice(1)) |
|
|
.join(' '); |
|
|
} |
|
|
|
|
|
|
|
|
function escapeHtml(text) { |
|
|
const div = document.createElement('div'); |
|
|
div.textContent = text; |
|
|
return div.innerHTML; |
|
|
} |
|
|
|
|
|
|
|
|
function updateProjectCount() { |
|
|
const count = document.getElementById('project-count'); |
|
|
count.textContent = `(${filteredProjects.length} of ${allProjects.length})`; |
|
|
} |
|
|
|
|
|
|
|
|
function setupEventListeners() { |
|
|
|
|
|
document.querySelectorAll('#platform-filters .filter-btn').forEach(btn => { |
|
|
btn.addEventListener('click', () => { |
|
|
|
|
|
document.querySelectorAll('#platform-filters .filter-btn').forEach(b => { |
|
|
b.classList.remove('active'); |
|
|
}); |
|
|
|
|
|
|
|
|
btn.classList.add('active'); |
|
|
|
|
|
|
|
|
currentPlatformFilter = btn.dataset.filter; |
|
|
applyFilters(); |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
document.querySelectorAll('#usecase-filters .filter-btn').forEach(btn => { |
|
|
btn.addEventListener('click', () => { |
|
|
|
|
|
document.querySelectorAll('#usecase-filters .filter-btn').forEach(b => { |
|
|
b.classList.remove('active'); |
|
|
}); |
|
|
|
|
|
|
|
|
btn.classList.add('active'); |
|
|
|
|
|
|
|
|
currentUsecaseFilter = btn.dataset.filter; |
|
|
applyFilters(); |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
const searchInput = document.getElementById('search-input'); |
|
|
searchInput.addEventListener('input', (e) => { |
|
|
currentSearchQuery = e.target.value; |
|
|
applyFilters(); |
|
|
}); |
|
|
|
|
|
|
|
|
const sortSelect = document.getElementById('sort-select'); |
|
|
sortSelect.addEventListener('change', (e) => { |
|
|
currentSort = e.target.value; |
|
|
sortProjects(); |
|
|
renderProjects(); |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
document.addEventListener('DOMContentLoaded', () => { |
|
|
setupEventListeners(); |
|
|
loadProjects(); |
|
|
}); |
|
|
|