| | |
| | 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(); |
| | }); |
| |
|