UI overhaul

This commit is contained in:
overcuriousity
2025-07-27 17:31:15 +02:00
parent 0d22210040
commit 0adabad94d
7 changed files with 192 additions and 104 deletions

View File

@@ -444,39 +444,46 @@ document.addEventListener('DOMContentLoaded', () => {
}
};
// Smart Prompting Input Handling
// Smart Prompting Input Handling - Fixed Race Conditions
aiInput.addEventListener('input', () => {
console.log('[DEBUG] Input event triggered, length:', aiInput.value.trim().length);
const inputLength = aiInput.value.trim().length;
// Clear existing timeout
// Clear ALL existing timeouts and abort controllers
clearTimeout(enhancementTimeout);
// Cancel any pending enhancement call
if (enhancementAbortController) {
enhancementAbortController.abort();
enhancementAbortController = null;
}
// Hide suggestions if input is too short
// Hide suggestions immediately if input is too short
if (inputLength < 40) {
showPromptingStatus('hidden');
return;
}
// Show analyzing state after 1 second
setTimeout(() => {
if (aiInput.value.trim().length >= 50) {
showPromptingStatus('analyzing');
}
}, 1000);
// Trigger AI enhancement after 1.5 seconds
// Single consolidated timeout for all smart prompting logic
enhancementTimeout = setTimeout(() => {
console.log('[DEBUG] Enhancement timeout fired, calling triggerSmartPrompting');
if (aiInput.value.trim().length >= 40) {
triggerSmartPrompting();
const currentLength = aiInput.value.trim().length;
// Double-check length hasn't changed during timeout
if (currentLength < 40) {
showPromptingStatus('hidden');
return;
}
}, 1500);
// Show analyzing state first
if (currentLength >= 50) {
showPromptingStatus('analyzing');
// Trigger enhancement after showing analyzing state
setTimeout(() => {
if (aiInput.value.trim().length >= 50) {
triggerSmartPrompting();
}
}, 500);
}
}, 1000); // Single timeout instead of multiple
});
aiInput.addEventListener('input', updateCharacterCount);

View File

@@ -68,40 +68,59 @@ const displayedScenarios = scenarios.slice(0, maxDisplayed);
<script define:vars={{ allScenarios: scenarios, maxDisplay: maxDisplayed }}>
let showingAllScenarios = false;
// Apply scenario search using existing search functionality
window.applyScenarioSearch = function(scenarioId) {
console.log(`Applying scenario search: ${scenarioId}`);
// Find the main search input (existing)
const clickedChip = document.querySelector(`[data-scenario-id="${scenarioId}"]`);
const mainSearchInput = document.getElementById('search-input');
if (mainSearchInput) {
// Use scenario ID as search term (it should match tool tags)
mainSearchInput.value = scenarioId;
if (!mainSearchInput) return;
// Check if this scenario is already active (allow deselection)
if (clickedChip && clickedChip.classList.contains('active')) {
// Deselect: clear search and remove active state
mainSearchInput.value = '';
document.querySelectorAll('.suggestion-chip').forEach(chip => {
chip.classList.remove('active');
});
// Trigger existing search functionality
// Clear the targeted search input too
const targetedInput = document.getElementById('targeted-search-input');
if (targetedInput) {
targetedInput.value = '';
}
// Trigger search to show all results
const inputEvent = new Event('input', { bubbles: true });
mainSearchInput.dispatchEvent(inputEvent);
// Switch to grid view
const gridToggle = document.querySelector('.view-toggle[data-view="grid"]');
if (gridToggle && !gridToggle.classList.contains('active')) {
gridToggle.click();
}
// Scroll to results
setTimeout(() => {
const toolsGrid = document.getElementById('tools-grid');
if (toolsGrid) {
toolsGrid.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
}, 200);
return;
}
// Apply new search
mainSearchInput.value = scenarioId;
// Trigger existing search functionality
const inputEvent = new Event('input', { bubbles: true });
mainSearchInput.dispatchEvent(inputEvent);
// Switch to grid view
const gridToggle = document.querySelector('.view-toggle[data-view="grid"]');
if (gridToggle && !gridToggle.classList.contains('active')) {
gridToggle.click();
}
// Visual feedback
document.querySelectorAll('.suggestion-chip').forEach(chip => {
chip.classList.remove('active');
});
document.querySelector(`[data-scenario-id="${scenarioId}"]`)?.classList.add('active');
if (clickedChip) {
clickedChip.classList.add('active');
}
// Scroll to results with better positioning
window.scrollToElementById('tools-grid');
};
// Toggle showing all scenarios
@@ -149,18 +168,14 @@ const displayedScenarios = scenarios.slice(0, maxDisplayed);
targetedInput.addEventListener('keydown', (e) => {
if (e.key === 'Enter') {
e.preventDefault();
// Switch to grid view and scroll to results
const gridToggle = document.querySelector('.view-toggle[data-view="grid"]');
if (gridToggle) {
e.preventDefault();
// Switch to grid view and scroll to results
const gridToggle = document.querySelector('.view-toggle[data-view="grid"]');
if (gridToggle) {
gridToggle.click();
setTimeout(() => {
const toolsGrid = document.getElementById('tools-grid');
if (toolsGrid) {
toolsGrid.scrollIntoView({ behavior: 'smooth' });
}
}, 100);
}
// Use consolidated scroll utility
window.scrollToElementById('tools-grid');
}
}
});
}

View File

@@ -117,15 +117,24 @@ const sortedTags = Object.entries(tagFrequency)
window.toolsData = toolsData;
document.addEventListener('DOMContentLoaded', () => {
const searchInput = document.getElementById('search-input');
const domainSelect = document.getElementById('domain-select');
const phaseButtons = document.querySelectorAll('.phase-button');
const proprietaryCheckbox = document.getElementById('include-proprietary');
const tagCloudItems = document.querySelectorAll('.tag-cloud-item');
const tagCloud = document.getElementById('tag-cloud');
const tagCloudToggle = document.getElementById('tag-cloud-toggle');
const viewToggles = document.querySelectorAll('.view-toggle');
const aiViewToggle = document.getElementById('ai-view-toggle');
// Cache DOM elements once
const elements = {
searchInput: document.getElementById('search-input'),
domainSelect: document.getElementById('domain-select'),
phaseButtons: document.querySelectorAll('.phase-button'),
proprietaryCheckbox: document.getElementById('include-proprietary'),
tagCloudItems: document.querySelectorAll('.tag-cloud-item'),
tagCloud: document.getElementById('tag-cloud'),
tagCloudToggle: document.getElementById('tag-cloud-toggle'),
viewToggles: document.querySelectorAll('.view-toggle'),
aiViewToggle: document.getElementById('ai-view-toggle')
};
// Verify critical elements exist
if (!elements.searchInput || !elements.domainSelect || !elements.proprietaryCheckbox) {
console.error('Critical filter elements not found');
return;
}
let selectedTags = new Set();
let selectedPhase = '';
@@ -133,7 +142,7 @@ const sortedTags = Object.entries(tagFrequency)
function initTagCloud() {
const visibleCount = 22;
tagCloudItems.forEach((item, index) => {
elements.tagCloudItems.forEach((item, index) => {
if (index >= visibleCount) {
item.style.display = 'none';
}
@@ -145,22 +154,22 @@ const sortedTags = Object.entries(tagFrequency)
const visibleCount = 22;
if (isTagCloudExpanded) {
tagCloud.classList.add('expanded');
tagCloudToggle.textContent = 'Weniger zeigen';
tagCloudToggle.setAttribute('data-expanded', 'true');
elements.tagCloud.classList.add('expanded');
elements.tagCloudToggle.textContent = 'Weniger zeigen';
elements.tagCloudToggle.setAttribute('data-expanded', 'true');
tagCloudItems.forEach(item => {
elements.tagCloudItems.forEach(item => {
if (!item.classList.contains('hidden')) {
item.style.display = 'inline-flex';
}
});
} else {
tagCloud.classList.remove('expanded');
tagCloudToggle.textContent = 'Mehr zeigen';
tagCloudToggle.setAttribute('data-expanded', 'false');
elements.tagCloud.classList.remove('expanded');
elements.tagCloudToggle.textContent = 'Mehr zeigen';
elements.tagCloudToggle.setAttribute('data-expanded', 'false');
let visibleIndex = 0;
tagCloudItems.forEach(item => {
elements.tagCloudItems.forEach(item => {
if (!item.classList.contains('hidden')) {
if (visibleIndex < visibleCount) {
item.style.display = 'inline-flex';
@@ -174,11 +183,11 @@ const sortedTags = Object.entries(tagFrequency)
}
function filterTagCloud() {
const searchTerm = searchInput.value.toLowerCase();
const searchTerm = elements.searchInput.value.toLowerCase();
let visibleCount = 0;
const maxVisibleWhenCollapsed = 22;
tagCloudItems.forEach(item => {
elements.tagCloudItems.forEach(item => {
const tagName = item.getAttribute('data-tag').toLowerCase();
const shouldShow = tagName.includes(searchTerm);
@@ -196,10 +205,10 @@ const sortedTags = Object.entries(tagFrequency)
}
});
const hasHiddenTags = Array.from(tagCloudItems).some(item =>
const hasHiddenTags = Array.from(elements.tagCloudItems).some(item =>
!item.classList.contains('hidden') && item.style.display === 'none'
);
tagCloudToggle.style.display = hasHiddenTags ? 'block' : 'none';
elements.tagCloudToggle.style.display = hasHiddenTags ? 'block' : 'none';
}
function isToolHosted(tool) {
@@ -224,7 +233,7 @@ const sortedTags = Object.entries(tagFrequency)
el.classList.remove('highlight-row', 'highlight-column');
});
const selectedDomain = domainSelect.value;
const selectedDomain = elements.domainSelect.value;
if (selectedDomain) {
const domainRow = matrixTable.querySelector(`tr[data-domain="${selectedDomain}"]`);
@@ -252,9 +261,9 @@ const sortedTags = Object.entries(tagFrequency)
}
function filterTools() {
const searchTerm = searchInput.value.toLowerCase();
const selectedDomain = domainSelect.value;
const includeProprietary = proprietaryCheckbox.checked;
const searchTerm = elements.searchInput.value.toLowerCase();
const selectedDomain = elements.domainSelect.value;
const includeProprietary = elements.proprietaryCheckbox.checked;
const filtered = window.toolsData.filter(tool => {
const domains = tool.domains || [];
@@ -314,7 +323,7 @@ const sortedTags = Object.entries(tagFrequency)
selectedPhase = '';
button.classList.remove('active');
} else {
phaseButtons.forEach(btn => btn.classList.remove('active'));
elements.phaseButtons.forEach(btn => btn.classList.remove('active'));
selectedPhase = phase;
button.classList.add('active');
}
@@ -323,7 +332,7 @@ const sortedTags = Object.entries(tagFrequency)
}
function handleViewToggle(view) {
viewToggles.forEach(btn => {
elements.viewToggles.forEach(btn => {
btn.classList.toggle('active', btn.getAttribute('data-view') === view);
});
@@ -339,37 +348,38 @@ const sortedTags = Object.entries(tagFrequency)
function clearTagFilters() {
selectedTags.clear();
tagCloudItems.forEach(item => item.classList.remove('active'));
elements.tagCloudItems.forEach(item => item.classList.remove('active'));
filterTools();
}
function clearAllFilters() {
searchInput.value = '';
domainSelect.value = '';
elements.searchInput.value = '';
elements.domainSelect.value = '';
selectedPhase = '';
phaseButtons.forEach(btn => btn.classList.remove('active'));
elements.phaseButtons.forEach(btn => btn.classList.remove('active'));
clearTagFilters();
filterTagCloud();
}
searchInput.addEventListener('input', () => {
// Event listeners using cached elements
elements.searchInput.addEventListener('input', () => {
filterTagCloud();
filterTools();
});
domainSelect.addEventListener('change', filterTools);
proprietaryCheckbox.addEventListener('change', filterTools);
tagCloudToggle.addEventListener('click', toggleTagCloud);
elements.domainSelect.addEventListener('change', filterTools);
elements.proprietaryCheckbox.addEventListener('change', filterTools);
elements.tagCloudToggle.addEventListener('click', toggleTagCloud);
tagCloudItems.forEach(item => {
elements.tagCloudItems.forEach(item => {
item.addEventListener('click', () => handleTagClick(item));
});
phaseButtons.forEach(btn => {
elements.phaseButtons.forEach(btn => {
btn.addEventListener('click', () => handlePhaseClick(btn));
});
viewToggles.forEach(btn => {
elements.viewToggles.forEach(btn => {
btn.addEventListener('click', () => handleViewToggle(btn.getAttribute('data-view')));
});

View File

@@ -672,6 +672,13 @@ domains.forEach((domain: any) => {
const primaryModal = document.getElementById('tool-details-primary');
const secondaryModal = document.getElementById('tool-details-secondary');
// Debounce rapid calls
if (window.modalHideInProgress) return;
window.modalHideInProgress = true;
setTimeout(() => {
window.modalHideInProgress = false;
}, 100);
if (modalType === 'both' || modalType === 'all') {
if (primaryModal) {
@@ -702,13 +709,19 @@ domains.forEach((domain: any) => {
if (contributeButtonSecondary) contributeButtonSecondary.style.display = 'none';
}
// Consolidated state checking with safety checks
const primaryActive = primaryModal && primaryModal.classList.contains('active');
const secondaryActive = secondaryModal && secondaryModal.classList.contains('active');
// Update overlay and body classes atomically
if (!primaryActive && !secondaryActive) {
if (overlay) overlay.classList.remove('active');
document.body.classList.remove('modals-side-by-side');
} else if (primaryActive !== secondaryActive) {
} else if (primaryActive && secondaryActive) {
// Both active - ensure side-by-side class
document.body.classList.add('modals-side-by-side');
} else {
// Only one active - remove side-by-side class
document.body.classList.remove('modals-side-by-side');
}
};