iteration UI overhaul

This commit is contained in:
overcuriousity 2025-07-27 16:51:28 +02:00
parent 1f9df5c1a8
commit 0d22210040
5 changed files with 399 additions and 8 deletions

View File

@ -0,0 +1,168 @@
---
import { getToolsData } from '../utils/dataService.js';
const data = await getToolsData();
const scenarios = data.scenarios || [];
// Configuration
const maxDisplayed = 9;
const displayedScenarios = scenarios.slice(0, maxDisplayed);
---
<section id="targeted-section" class="targeted-section">
<div class="targeted-header">
<h3>Gezielte Tool-Suche</h3>
<p class="targeted-subtitle">
Finden Sie schnell das passende Werkzeug für Ihre spezifische Anforderung
</p>
</div>
<div class="search-interface">
<div class="search-box">
<div class="search-icon">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="11" cy="11" r="8"/>
<line x1="21" y1="21" x2="16.65" y2="16.65"/>
</svg>
</div>
<input
type="text"
class="targeted-search-input"
placeholder="z.B. 'Windows Registry analysieren' oder 'PCAP-Dateien auswerten'..."
id="targeted-search-input"
/>
</div>
{scenarios.length > 0 && (
<div class="search-suggestions" id="scenario-suggestions">
{displayedScenarios.map((scenario) => (
<div
class="suggestion-chip"
data-scenario-id={scenario.id}
onclick={`applyScenarioSearch('${scenario.id}')`}
>
<span class="scenario-emoji">{scenario.icon}</span>
<span class="scenario-text">{scenario.friendly_name}</span>
</div>
))}
</div>
)}
{scenarios.length > maxDisplayed && (
<div class="more-scenarios">
<button class="btn-more-scenarios" onclick="toggleAllScenarios()" id="more-scenarios-btn">
+ {scenarios.length - maxDisplayed} weitere Szenarien
</button>
</div>
)}
</div>
<div class="targeted-tip">
<p>
<strong>Tipp:</strong> Die Szenarien durchsuchen automatisch nach passenden Tags und Beschreibungen.
Für KI-gestützte Empfehlungen nutzen Sie den entsprechenden Modus.
</p>
</div>
</section>
<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 mainSearchInput = document.getElementById('search-input');
if (mainSearchInput) {
// Use scenario ID as search term (it should match tool tags)
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();
}
// Scroll to results
setTimeout(() => {
const toolsGrid = document.getElementById('tools-grid');
if (toolsGrid) {
toolsGrid.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
}, 200);
}
// Visual feedback
document.querySelectorAll('.suggestion-chip').forEach(chip => {
chip.classList.remove('active');
});
document.querySelector(`[data-scenario-id="${scenarioId}"]`)?.classList.add('active');
};
// Toggle showing all scenarios
window.toggleAllScenarios = function() {
const suggestionsContainer = document.getElementById('scenario-suggestions');
const moreBtn = document.getElementById('more-scenarios-btn');
if (!showingAllScenarios) {
// Show additional scenarios
const additionalScenarios = allScenarios.slice(maxDisplay);
additionalScenarios.forEach(scenario => {
const chip = document.createElement('div');
chip.className = 'suggestion-chip additional-scenario';
chip.setAttribute('data-scenario-id', scenario.id);
chip.onclick = () => applyScenarioSearch(scenario.id);
chip.innerHTML = `
<span class="scenario-emoji">${scenario.icon}</span>
<span class="scenario-text">${scenario.friendly_name}</span>
`;
suggestionsContainer.appendChild(chip);
});
moreBtn.textContent = 'Weniger anzeigen';
showingAllScenarios = true;
} else {
// Hide additional scenarios
document.querySelectorAll('.additional-scenario').forEach(chip => chip.remove());
moreBtn.textContent = `+ ${allScenarios.length - maxDisplay} weitere Szenarien`;
showingAllScenarios = false;
}
};
// Handle targeted search input
document.addEventListener('DOMContentLoaded', () => {
const targetedInput = document.getElementById('targeted-search-input');
if (targetedInput) {
targetedInput.addEventListener('input', (e) => {
const mainSearchInput = document.getElementById('search-input');
if (mainSearchInput && e.target.value.length > 2) {
mainSearchInput.value = e.target.value;
const inputEvent = new Event('input', { bubbles: true });
mainSearchInput.dispatchEvent(inputEvent);
}
});
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) {
gridToggle.click();
setTimeout(() => {
const toolsGrid = document.getElementById('tools-grid');
if (toolsGrid) {
toolsGrid.scrollIntoView({ behavior: 'smooth' });
}
}, 100);
}
}
});
}
});
</script>

View File

@ -2153,3 +2153,34 @@ domain-agnostic-software:
- id: specific-os - id: specific-os
name: Betriebssysteme name: Betriebssysteme
description: Operating Systems which focus on forensics description: Operating Systems which focus on forensics
scenarios:
- id: registry
icon: 🗃️
friendly_name: "Registry-Analyse"
- id: memory-forensics
icon: 🧠
friendly_name: "Memory-Forensik"
- id: network-traffic
icon: 🌐
friendly_name: "Netzwerk-Traffic"
- id: mobile-forensik
icon: 📱
friendly_name: "Mobile Geräte"
- id: malware-analysis
icon: 🦠
friendly_name: "Malware-Analyse"
- id: timeline-analysis
icon:
friendly_name: "Timeline-Erstellung"
- id: file-recovery
icon: 💾
friendly_name: "Datei-Wiederherstellung"
- id: browser-forensik
icon: 🌍
friendly_name: "Browser-Forensik"
- id: email-forensik
icon: 📧
friendly_name: "E-Mail-Forensik"
- id: log-analysis
icon: 📊
friendly_name: "Log-Analyse"

View File

@ -4,6 +4,7 @@ import ToolCard from '../components/ToolCard.astro';
import ToolFilters from '../components/ToolFilters.astro'; import ToolFilters from '../components/ToolFilters.astro';
import ToolMatrix from '../components/ToolMatrix.astro'; import ToolMatrix from '../components/ToolMatrix.astro';
import AIQueryInterface from '../components/AIQueryInterface.astro'; import AIQueryInterface from '../components/AIQueryInterface.astro';
import TargetedScenarios from '../components/TargetedScenarios.astro';
import { getToolsData } from '../utils/dataService.js'; import { getToolsData } from '../utils/dataService.js';
const data = await getToolsData(); const data = await getToolsData();
@ -140,6 +141,8 @@ const phases = data.phases;
</div> </div>
</section> </section>
<TargetedScenarios />
<section id="filters-section" style="padding: 2rem 0;"> <section id="filters-section" style="padding: 2rem 0;">
<div style="text-align: center; margin-bottom: 2rem;"> <div style="text-align: center; margin-bottom: 2rem;">
<h3 style="color: var(--color-text); margin-bottom: 0.5rem;">Alle verfügbaren Werkzeuge durchsuchen</h3> <h3 style="color: var(--color-text); margin-bottom: 0.5rem;">Alle verfügbaren Werkzeuge durchsuchen</h3>
@ -192,15 +195,11 @@ const phases = data.phases;
methodologySection.scrollIntoView({ behavior: 'smooth', block: 'start' }); methodologySection.scrollIntoView({ behavior: 'smooth', block: 'start' });
} }
} else if (approach === 'targeted') { } else if (approach === 'targeted') {
// Show targeted scenarios section (implemented in Phase 3) // Show targeted scenarios section
const targetedSection = document.getElementById('targeted-section'); const targetedSection = document.getElementById('targeted-section');
if (targetedSection) { if (targetedSection) {
targetedSection.classList.add('active'); targetedSection.classList.add('active');
targetedSection.scrollIntoView({ behavior: 'smooth', block: 'start' }); targetedSection.scrollIntoView({ behavior: 'smooth', block: 'start' });
} else {
// For now, show a placeholder message
alert('Gezielte Szenarien werden in Phase 3 implementiert. Verwenden Sie vorerst die Standard-Werkzeugauswahl unten.');
document.getElementById('filters-section').scrollIntoView({ behavior: 'smooth' });
} }
} }
}; };

View File

@ -1928,6 +1928,26 @@ footer {
grid-template-columns: 1fr 1fr; grid-template-columns: 1fr 1fr;
gap: 0.75rem; gap: 0.75rem;
} }
.search-suggestions {
gap: 0.5rem;
}
.suggestion-chip {
padding: 0.5rem 0.75rem;
font-size: 0.8125rem;
}
.scenario-emoji {
font-size: 1rem;
}
.targeted-search-input {
padding: 0.875rem 0.875rem 0.875rem 2.75rem;
}
.search-icon {
left: 0.875rem;
}
} }
@media (width <= 640px) { @media (width <= 640px) {
@ -2007,6 +2027,20 @@ footer {
padding: 0.5rem 1rem; padding: 0.5rem 1rem;
flex: 1; flex: 1;
} }
.search-suggestions {
justify-content: center;
}
.suggestion-chip {
flex: 1;
min-width: 0;
max-width: calc(50% - 0.25rem);
justify-content: center;
}
.targeted-section {
padding: 1.5rem;
}
} }
@media (width <= 480px) { @media (width <= 480px) {
@ -2324,6 +2358,80 @@ footer {
margin-right: 0.5rem; margin-right: 0.5rem;
} }
.suggestion-chip {
display: inline-flex;
align-items: center;
gap: 0.5rem;
padding: 0.75rem 1rem;
background-color: var(--color-bg-secondary);
border: 1px solid var(--color-border);
border-radius: 2rem;
cursor: pointer;
transition: var(--transition-fast);
font-size: 0.875rem;
user-select: none;
}
.suggestion-chip:hover {
background-color: var(--color-accent);
border-color: var(--color-accent);
color: white;
transform: translateY(-2px);
box-shadow: var(--shadow-md);
}
.suggestion-chip.active {
background-color: var(--color-accent);
border-color: var(--color-accent);
color: white;
}
.scenario-emoji {
font-size: 1.125rem;
}
.scenario-text {
font-weight: 500;
}
.more-scenarios {
text-align: center;
margin-top: 1rem;
}
.btn-more-scenarios {
background: none;
border: 1px solid var(--color-border);
border-radius: 1rem;
padding: 0.5rem 1rem;
color: var(--color-text-secondary);
cursor: pointer;
font-size: 0.8125rem;
transition: var(--transition-fast);
}
.btn-more-scenarios:hover {
background-color: var(--color-bg-secondary);
border-color: var(--color-accent);
color: var(--color-accent);
}
.targeted-tip {
background-color: var(--color-bg-secondary);
border: 1px solid var(--color-border);
border-radius: 0.5rem;
padding: 1rem;
text-align: center;
max-width: 600px;
margin: 0 auto;
}
.targeted-tip p {
margin: 0;
font-size: 0.875rem;
color: var(--color-text-secondary);
}
.dismiss-button { .dismiss-button {
align-self: flex-end; align-self: flex-end;
background: none; background: none;
@ -2735,3 +2843,78 @@ footer {
gap: 1rem; gap: 1rem;
margin-bottom: 2rem; margin-bottom: 2rem;
} }
.targeted-section {
background: var(--color-bg);
border: 1px solid var(--color-border);
border-radius: 0.75rem;
padding: 2rem;
margin: 2rem 0;
display: none;
animation: fadeInUp 0.5s ease-out;
}
.targeted-section.active {
display: block;
}
.targeted-header {
text-align: center;
margin-bottom: 2rem;
}
.targeted-header h3 {
color: var(--color-accent);
font-size: 1.5rem;
margin-bottom: 0.5rem;
}
.targeted-subtitle {
color: var(--color-text-secondary);
font-size: 0.875rem;
margin: 0;
}
.search-interface {
max-width: 800px;
margin: 0 auto 2rem;
}
.search-box {
position: relative;
margin-bottom: 1.5rem;
}
.search-icon {
position: absolute;
left: 1rem;
top: 50%;
transform: translateY(-50%);
color: var(--color-text-secondary);
pointer-events: none;
}
.targeted-search-input {
width: 100%;
padding: 1rem 1rem 1rem 3rem;
border: 2px solid var(--color-border);
border-radius: 0.5rem;
font-size: 0.875rem;
background-color: var(--color-bg);
color: var(--color-text);
transition: var(--transition-fast);
}
.targeted-search-input:focus {
outline: none;
border-color: var(--color-accent);
box-shadow: 0 0 0 3px rgb(5 150 105 / 10%);
}
.search-suggestions {
display: flex;
flex-wrap: wrap;
gap: 0.75rem;
margin-bottom: 1rem;
justify-content: center;
}

View File

@ -39,6 +39,11 @@ const ToolsDataSchema = z.object({
name: z.string(), name: z.string(),
description: z.string().optional() description: z.string().optional()
})).optional().default([]), })).optional().default([]),
scenarios: z.array(z.object({
id: z.string(),
icon: z.string(),
friendly_name: z.string()
})).optional().default([]),
}); });
interface ToolsData { interface ToolsData {
@ -46,11 +51,15 @@ interface ToolsData {
domains: any[]; domains: any[];
phases: any[]; phases: any[];
'domain-agnostic-software': any[]; 'domain-agnostic-software': any[];
scenarios: any[];
} }
interface CompressedToolsData extends Omit<ToolsData, 'tools'> { interface CompressedToolsData {
tools: any[]; tools: any[];
concepts: any[]; concepts: any[];
domains: any[];
phases: any[];
'domain-agnostic-software': any[];
} }
let cachedData: ToolsData | null = null; let cachedData: ToolsData | null = null;
@ -144,6 +153,7 @@ export async function getCompressedToolsDataForAI(): Promise<CompressedToolsData
domains: data.domains, domains: data.domains,
phases: data.phases, phases: data.phases,
'domain-agnostic-software': data['domain-agnostic-software'] 'domain-agnostic-software': data['domain-agnostic-software']
// scenarios intentionally excluded from AI data
}; };
} }