From fcab1485f578b281ae771c13fc147bcaa2c91f48 Mon Sep 17 00:00:00 2001 From: overcuriousity Date: Tue, 29 Jul 2025 22:43:23 +0200 Subject: [PATCH] interface improvements --- src/components/ToolFilters.astro | 662 +++++++++++++++++++++---------- src/components/ToolMatrix.astro | 4 +- src/pages/index.astro | 28 +- src/styles/global.css | 519 ++++++++++++++++++++---- 4 files changed, 905 insertions(+), 308 deletions(-) diff --git a/src/components/ToolFilters.astro b/src/components/ToolFilters.astro index 15ec517..df2a00a 100644 --- a/src/components/ToolFilters.astro +++ b/src/components/ToolFilters.astro @@ -2,10 +2,16 @@ import { getToolsData } from '../utils/dataService.js'; const data = await getToolsData(); - const domains = data.domains; const phases = data.phases; +// Extract unique values dynamically - NO HARD-CODING +const skillLevels = [...new Set(data.tools.map(tool => tool.skillLevel))].filter(Boolean).sort(); +const platforms = [...new Set(data.tools.flatMap(tool => tool.platforms || []))].filter(Boolean).sort(); +const licenses = [...new Set(data.tools.map(tool => tool.license))].filter(Boolean).sort(); +const toolTypes = [...new Set(data.tools.map(tool => tool.type))].filter(Boolean).sort(); +const accessTypes = [...new Set(data.tools.map(tool => tool.accessType))].filter(Boolean).sort(); + const tagFrequency = data.tools.reduce((acc: Record, tool: any) => { tool.tags.forEach((tag: string) => { acc[tag] = (acc[tag] || 0) + 1; @@ -19,97 +25,243 @@ const sortedTags = Object.entries(tagFrequency) ---
-
- -
- -
-
- - -
- -
- -
- {phases.map((phase: any) => ( - - ))} + +
+
+
+

🔍 Suche

-
-
- -
-
- - -
- -
-
- -
-
- {sortedTags.map((tag, index) => ( - - ))} +
+
+ + +
+
+
+

🎯 Primäre Filter

+ +
+ +
+
+ + +
+ +
+ + +
- -
- - - - + + +
+
+
+

⚙️ Erweiterte Filter

+ +
+ +
+
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+
+ +
+ + + +
+
+
+
+ + +
+
+
+

🏷️ Tag-Filter

+
+ + +
+
+ +
+
+
+ {sortedTags.map((tag, index) => ( + + ))} +
+
+
+
+ + +
+
+
+

👁️ Ansicht

+
+ Alle Tools +
+
+ +
+ + + + + + + +
+
@@ -117,31 +269,55 @@ const sortedTags = Object.entries(tagFrequency) window.toolsData = toolsData; document.addEventListener('DOMContentLoaded', () => { - // Cache DOM elements once + // Cache DOM elements const elements = { searchInput: document.getElementById('search-input'), + clearSearch: document.getElementById('clear-search'), domainSelect: document.getElementById('domain-select'), - phaseButtons: document.querySelectorAll('.phase-button'), - proprietaryCheckbox: document.getElementById('include-proprietary'), + phaseSelect: document.getElementById('phase-select'), + typeSelect: document.getElementById('type-select'), + skillSelect: document.getElementById('skill-select'), + platformSelect: document.getElementById('platform-select'), + licenseSelect: document.getElementById('license-select'), + accessSelect: document.getElementById('access-select'), + hostedOnly: document.getElementById('hosted-only'), + knowledgebaseOnly: document.getElementById('knowledgebase-only'), tagCloudItems: document.querySelectorAll('.tag-cloud-item'), tagCloud: document.getElementById('tag-cloud'), tagCloudToggle: document.getElementById('tag-cloud-toggle'), + selectedTags: document.getElementById('selected-tags'), viewToggles: document.querySelectorAll('.view-toggle'), - aiViewToggle: document.getElementById('ai-view-toggle') + resultsCounter: document.getElementById('results-counter'), + resetButtons: { + primary: document.getElementById('reset-primary'), + advanced: document.getElementById('reset-advanced'), + tags: document.getElementById('reset-tags'), + all: document.getElementById('reset-all-filters') + } }; // Verify critical elements exist - if (!elements.searchInput || !elements.domainSelect || !elements.proprietaryCheckbox) { + if (!elements.searchInput || !elements.domainSelect) { console.error('Critical filter elements not found'); return; } + // State management let selectedTags = new Set(); let selectedPhase = ''; let isTagCloudExpanded = false; + // Helper function to check if tool is hosted + function isToolHosted(tool) { + return tool.projectUrl !== undefined && + tool.projectUrl !== null && + tool.projectUrl !== "" && + tool.projectUrl.trim() !== ""; + } + + // Initialize tag cloud function initTagCloud() { - const visibleCount = 22; + const visibleCount = 20; elements.tagCloudItems.forEach((item, index) => { if (index >= visibleCount) { item.style.display = 'none'; @@ -149,9 +325,10 @@ const sortedTags = Object.entries(tagFrequency) }); } + // Toggle tag cloud expansion function toggleTagCloud() { isTagCloudExpanded = !isTagCloudExpanded; - const visibleCount = 22; + const visibleCount = 20; if (isTagCloudExpanded) { elements.tagCloud.classList.add('expanded'); @@ -181,11 +358,12 @@ const sortedTags = Object.entries(tagFrequency) }); } } - + + // Filter tag cloud based on search function filterTagCloud() { const searchTerm = elements.searchInput.value.toLowerCase(); let visibleCount = 0; - const maxVisibleWhenCollapsed = 22; + const maxVisibleWhenCollapsed = 20; elements.tagCloudItems.forEach(item => { const tagName = item.getAttribute('data-tag').toLowerCase(); @@ -211,91 +389,132 @@ const sortedTags = Object.entries(tagFrequency) elements.tagCloudToggle.style.display = hasHiddenTags ? 'block' : 'none'; } - function isToolHosted(tool) { - return tool.projectUrl !== undefined && - tool.projectUrl !== null && - tool.projectUrl !== "" && - tool.projectUrl.trim() !== ""; - } - - function isMethod(tool) { - return tool.type === 'method'; - } - - function updateMatrixHighlighting() { - const currentView = document.querySelector('.view-toggle.active')?.getAttribute('data-view'); - if (currentView !== 'matrix') return; + // Update selected tags display + function updateSelectedTags() { + if (selectedTags.size === 0) { + elements.selectedTags.style.display = 'none'; + return; + } - const matrixTable = document.querySelector('.matrix-table'); - if (!matrixTable) return; + elements.selectedTags.style.display = 'block'; + elements.selectedTags.innerHTML = ` +
Ausgewählte Tags:
+
+ ${Array.from(selectedTags).map(tag => ` + + ${tag} + + + `).join('')} +
+ `; - matrixTable.querySelectorAll('.highlight-row, .highlight-column').forEach(el => { - el.classList.remove('highlight-row', 'highlight-column'); + // Add event listeners for remove buttons + elements.selectedTags.querySelectorAll('.remove-tag').forEach(btn => { + btn.addEventListener('click', () => { + const tag = btn.getAttribute('data-tag'); + removeTag(tag); + }); }); - - const selectedDomain = elements.domainSelect.value; - - if (selectedDomain) { - const domainRow = matrixTable.querySelector(`tr[data-domain="${selectedDomain}"]`); - if (domainRow) { - domainRow.classList.add('highlight-row'); - } - } - - if (selectedPhase) { - const phaseHeaders = matrixTable.querySelectorAll('thead th[data-phase]'); - const phaseIndex = Array.from(phaseHeaders).findIndex(th => - th.getAttribute('data-phase') === selectedPhase - ); - - if (phaseIndex >= 0) { - const columnIndex = phaseIndex + 1; - matrixTable.querySelectorAll(`tr`).forEach(row => { - const cell = row.children[columnIndex]; - if (cell) { - cell.classList.add('highlight-column'); - } - }); - } - } } + // Add/remove tags + function addTag(tag) { + selectedTags.add(tag); + document.querySelector(`[data-tag="${tag}"]`).classList.add('active'); + updateSelectedTags(); + filterTools(); + } + + function removeTag(tag) { + selectedTags.delete(tag); + const tagElement = document.querySelector(`[data-tag="${tag}"]`); + if (tagElement) tagElement.classList.remove('active'); + updateSelectedTags(); + filterTools(); + } + + // Update results counter + function updateResultsCounter(count) { + const total = window.toolsData.length; + elements.resultsCounter.textContent = count === total + ? `${total} Tools` + : `${count} von ${total} Tools`; + } + + // Main filter function function filterTools() { - const searchTerm = elements.searchInput.value.trim(); + const searchTerm = elements.searchInput.value.trim().toLowerCase(); const selectedDomain = elements.domainSelect.value; - const includeProprietary = elements.proprietaryCheckbox.checked; + const selectedPhaseFromSelect = elements.phaseSelect.value; + const selectedType = elements.typeSelect.value; + const selectedSkill = elements.skillSelect.value; + const selectedPlatform = elements.platformSelect.value; + const selectedLicense = elements.licenseSelect.value; + const selectedAccess = elements.accessSelect.value; + const hostedOnly = elements.hostedOnly.checked; + const knowledgebaseOnly = elements.knowledgebaseOnly.checked; + + // Use phase from either dropdown or button selection + const activePhase = selectedPhaseFromSelect || selectedPhase; const filtered = window.toolsData.filter(tool => { - const domains = tool.domains || []; - const phases = tool.phases || []; - const tags = tool.tags || []; - - // Search filter (keep existing logic) + // Search filter if (searchTerm && !( - tool.name.toLowerCase().includes(searchTerm.toLowerCase()) || - tool.description.toLowerCase().includes(searchTerm.toLowerCase()) || - tags.some(tag => tag.toLowerCase().includes(searchTerm.toLowerCase())) + tool.name.toLowerCase().includes(searchTerm) || + tool.description.toLowerCase().includes(searchTerm) || + (tool.tags || []).some(tag => tag.toLowerCase().includes(searchTerm)) )) { return false; } // Domain filter - if (selectedDomain && !domains.includes(selectedDomain)) { + if (selectedDomain && !(tool.domains || []).includes(selectedDomain)) { return false; } // Phase filter - if (selectedPhase && !phases.includes(selectedPhase)) { + if (activePhase && !(tool.phases || []).includes(activePhase)) { return false; } - // Proprietary filter - if (!includeProprietary && !isMethod(tool) && tool.type !== 'concept' && tool.license === 'Proprietary') { + // Type filter + if (selectedType && tool.type !== selectedType) { + return false; + } + + // Skill level filter + if (selectedSkill && tool.skillLevel !== selectedSkill) { + return false; + } + + // Platform filter + if (selectedPlatform && !(tool.platforms || []).includes(selectedPlatform)) { + return false; + } + + // License filter - NO MORE HARD-CODED LOGIC + if (selectedLicense && tool.license !== selectedLicense) { + return false; + } + + // Access type filter + if (selectedAccess && tool.accessType !== selectedAccess) { + return false; + } + + // Hosted only filter (CC24-Server tools) + if (hostedOnly && !isToolHosted(tool)) { + return false; + } + + // Knowledgebase only filter + if (knowledgebaseOnly && !tool.knowledgebase) { return false; } // Tag filter - if (selectedTags.size > 0 && !Array.from(selectedTags).every(tag => tags.includes(tag))) { + if (selectedTags.size > 0 && !Array.from(selectedTags).every(tag => (tool.tags || []).includes(tag))) { return false; } @@ -307,97 +526,118 @@ const sortedTags = Object.entries(tagFrequency) ? window.prioritizeSearchResults(filtered, searchTerm) : filtered; - updateMatrixHighlighting(); + updateResultsCounter(finalResults.length); + // Dispatch event for other components window.dispatchEvent(new CustomEvent('toolsFiltered', { detail: finalResults })); } - function handleTagClick(tagItem) { - const tag = tagItem.getAttribute('data-tag'); - - if (selectedTags.has(tag)) { - selectedTags.delete(tag); - tagItem.classList.remove('active'); - } else { - selectedTags.add(tag); - tagItem.classList.add('active'); - } - + // Reset functions + function resetPrimaryFilters() { + elements.domainSelect.value = ''; + elements.phaseSelect.value = ''; + selectedPhase = ''; filterTools(); } - function handlePhaseClick(button) { - const phase = button.getAttribute('data-phase'); - - if (selectedPhase === phase) { - selectedPhase = ''; - button.classList.remove('active'); - } else { - elements.phaseButtons.forEach(btn => btn.classList.remove('active')); - selectedPhase = phase; - button.classList.add('active'); - } - + function resetAdvancedFilters() { + elements.typeSelect.value = ''; + elements.skillSelect.value = ''; + elements.platformSelect.value = ''; + elements.licenseSelect.value = ''; + elements.accessSelect.value = ''; + elements.hostedOnly.checked = false; + elements.knowledgebaseOnly.checked = false; filterTools(); } - function handleViewToggle(view) { - elements.viewToggles.forEach(btn => { - btn.classList.toggle('active', btn.getAttribute('data-view') === view); - }); - - window.dispatchEvent(new CustomEvent('viewChanged', { detail: view })); - - if (view === 'hosted') { - const hosted = window.toolsData.filter(tool => isToolHosted(tool)); - window.dispatchEvent(new CustomEvent('toolsFiltered', { detail: hosted })); - } else { - filterTools(); - } - } - - function clearTagFilters() { + function resetTags() { selectedTags.clear(); elements.tagCloudItems.forEach(item => item.classList.remove('active')); + updateSelectedTags(); filterTools(); } - function clearAllFilters() { + function resetAllFilters() { elements.searchInput.value = ''; - elements.domainSelect.value = ''; - selectedPhase = ''; - elements.phaseButtons.forEach(btn => btn.classList.remove('active')); - clearTagFilters(); + elements.clearSearch.classList.add('hidden'); + resetPrimaryFilters(); + resetAdvancedFilters(); + resetTags(); filterTagCloud(); } - // Event listeners using cached elements - elements.searchInput.addEventListener('input', () => { + // Event listeners + elements.searchInput.addEventListener('input', (e) => { + const hasValue = e.target.value.length > 0; + elements.clearSearch.classList.toggle('hidden', !hasValue); filterTagCloud(); filterTools(); }); - elements.domainSelect.addEventListener('change', filterTools); - elements.proprietaryCheckbox.addEventListener('change', filterTools); + elements.clearSearch.addEventListener('click', () => { + elements.searchInput.value = ''; + elements.clearSearch.classList.add('hidden'); + filterTagCloud(); + filterTools(); + }); + + [elements.domainSelect, elements.phaseSelect, elements.typeSelect, elements.skillSelect, + elements.platformSelect, elements.licenseSelect, elements.accessSelect].forEach(select => { + select.addEventListener('change', filterTools); + }); + + [elements.hostedOnly, elements.knowledgebaseOnly].forEach(checkbox => { + checkbox.addEventListener('change', filterTools); + }); + elements.tagCloudToggle.addEventListener('click', toggleTagCloud); elements.tagCloudItems.forEach(item => { - item.addEventListener('click', () => handleTagClick(item)); - }); - - elements.phaseButtons.forEach(btn => { - btn.addEventListener('click', () => handlePhaseClick(btn)); + item.addEventListener('click', () => { + const tag = item.getAttribute('data-tag'); + if (selectedTags.has(tag)) { + removeTag(tag); + } else { + addTag(tag); + } + }); }); elements.viewToggles.forEach(btn => { - btn.addEventListener('click', () => handleViewToggle(btn.getAttribute('data-view'))); + btn.addEventListener('click', () => { + const view = btn.getAttribute('data-view'); + + // Simple toggle like the old version + elements.viewToggles.forEach(b => { + b.classList.toggle('active', b.getAttribute('data-view') === view); + }); + + window.dispatchEvent(new CustomEvent('viewChanged', { detail: view })); + + if (view === 'hosted') { + const hosted = window.toolsData.filter(tool => isToolHosted(tool)); + window.dispatchEvent(new CustomEvent('toolsFiltered', { detail: hosted })); + } else { + filterTools(); + } + }); }); - window.clearTagFilters = clearTagFilters; - window.clearAllFilters = clearAllFilters; + // Reset button listeners + elements.resetButtons.primary.addEventListener('click', resetPrimaryFilters); + elements.resetButtons.advanced.addEventListener('click', resetAdvancedFilters); + elements.resetButtons.tags.addEventListener('click', resetTags); + elements.resetButtons.all.addEventListener('click', resetAllFilters); + // Expose functions globally for backwards compatibility + window.clearTagFilters = resetTags; + window.clearAllFilters = resetAllFilters; + + // Initialize initTagCloud(); filterTagCloud(); + updateSelectedTags(); setTimeout(() => { filterTools(); diff --git a/src/components/ToolMatrix.astro b/src/components/ToolMatrix.astro index 6f764bf..14514d9 100644 --- a/src/components/ToolMatrix.astro +++ b/src/components/ToolMatrix.astro @@ -194,8 +194,8 @@ domains.forEach((domain: any) => {