UI overhaul
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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')));
|
||||
});
|
||||
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user