introduce concepts phase 3 (tool modals)

This commit is contained in:
overcuriousity
2025-07-20 18:27:52 +02:00
parent 202ee5f801
commit e7800724bb
4 changed files with 297 additions and 66 deletions

View File

@@ -139,12 +139,14 @@ domains.forEach((domain: any) => {
</div>
</div>
<!-- Tool Details Modal stays the same -->
<div class="modal-overlay" id="modal-overlay" onclick="window.hideToolDetails()"></div>
<div class="tool-details" id="tool-details">
<!-- Tool Details Modals - Dual Modal System -->
<div class="modal-overlay" id="modal-overlay" onclick="window.hideAllToolDetails()"></div>
<!-- Primary Modal -->
<div class="tool-details" id="tool-details-primary">
<div style="display: flex; justify-content: space-between; align-items: start; margin-bottom: 1rem;">
<h2 id="tool-name" style="margin: 0;">Tool Name</h2>
<button class="btn-icon" onclick="window.hideToolDetails()">
<h2 id="tool-name-primary" style="margin: 0;">Tool Name</h2>
<button class="btn-icon" onclick="window.hideToolDetails('primary')">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
@@ -152,15 +154,38 @@ domains.forEach((domain: any) => {
</button>
</div>
<p id="tool-description" class="text-muted"></p>
<p id="tool-description-primary" class="text-muted"></p>
<div id="tool-badges" style="display: flex; gap: 0.5rem; margin-bottom: 1rem;"></div>
<div id="tool-badges-primary" style="display: flex; gap: 0.5rem; margin-bottom: 1rem;"></div>
<div id="tool-metadata" style="margin-bottom: 1rem;"></div>
<div id="tool-metadata-primary" style="margin-bottom: 1rem;"></div>
<div id="tool-tags" style="margin-bottom: 1rem;"></div>
<div id="tool-tags-primary" style="margin-bottom: 1rem;"></div>
<div id="tool-links" style="display: flex; gap: 0.5rem; flex-direction: column;"></div>
<div id="tool-links-primary" style="display: flex; gap: 0.5rem; flex-direction: column;"></div>
</div>
<!-- Secondary Modal -->
<div class="tool-details" id="tool-details-secondary">
<div style="display: flex; justify-content: space-between; align-items: start; margin-bottom: 1rem;">
<h2 id="tool-name-secondary" style="margin: 0;">Tool Name</h2>
<button class="btn-icon" onclick="window.hideToolDetails('secondary')">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
</button>
</div>
<p id="tool-description-secondary" class="text-muted"></p>
<div id="tool-badges-secondary" style="display: flex; gap: 0.5rem; margin-bottom: 1rem;"></div>
<div id="tool-metadata-secondary" style="margin-bottom: 1rem;"></div>
<div id="tool-tags-secondary" style="margin-bottom: 1rem;"></div>
<div id="tool-links-secondary" style="display: flex; gap: 0.5rem; flex-direction: column;"></div>
</div>
<script define:vars={{ toolsData: tools, domainAgnosticSoftware, domainAgnosticTools }}>
@@ -244,68 +269,131 @@ domains.forEach((domain: any) => {
// Make functions globally available
window.toggleDomainAgnosticSection = toggleDomainAgnosticSection;
// Tool details functions (unchanged)
window.showToolDetails = function(toolName) {
// Enhanced modal system for side-by-side display
window.showToolDetails = function(toolName, modalType = 'primary') {
const tool = toolsData.find(t => t.name === toolName);
if (!tool) return;
if (!tool) {
console.error('Tool not found:', toolName);
return;
}
const isMethod = tool.type === 'method';
const isConcept = tool.type === 'concept';
// Get modal-specific element IDs
const elements = {
name: document.getElementById(`tool-name-${modalType}`),
description: document.getElementById(`tool-description-${modalType}`),
badges: document.getElementById(`tool-badges-${modalType}`),
metadata: document.getElementById(`tool-metadata-${modalType}`),
tags: document.getElementById(`tool-tags-${modalType}`),
links: document.getElementById(`tool-links-${modalType}`)
};
// Check if all elements exist
for (const [key, element] of Object.entries(elements)) {
if (!element) {
console.error(`Element not found: tool-${key}-${modalType}`);
return;
}
}
// Update modal content
const toolNameElement = document.getElementById('tool-name');
const iconHtml = tool.icon ? `<span style="margin-right: 0.75rem; font-size: 1.5rem;">${tool.icon}</span>` : '';
toolNameElement.innerHTML = `${iconHtml}${tool.name}`;
document.getElementById('tool-description').textContent = tool.description;
elements.name.innerHTML = `${iconHtml}${tool.name}`;
elements.description.textContent = tool.description;
// Badges - Only CC24-Server and Knowledgebase
const badgesContainer = document.getElementById('tool-badges');
// Badges
const hasValidProjectUrl = tool.projectUrl !== undefined &&
tool.projectUrl !== null &&
tool.projectUrl !== "" &&
tool.projectUrl.trim() !== "";
badgesContainer.innerHTML = '';
// Only show CC24-Server and Knowledgebase badges
if (!isMethod && hasValidProjectUrl) {
badgesContainer.innerHTML += '<span class="badge badge-primary">CC24-Server</span>';
}
if (tool.knowledgebase === true) {
badgesContainer.innerHTML += '<span class="badge badge-error">📖</span>';
elements.badges.innerHTML = '';
if (isConcept) {
elements.badges.innerHTML += '<span class="badge" style="background-color: var(--color-concept); color: white;">Konzept</span>';
} else if (isMethod) {
elements.badges.innerHTML += '<span class="badge" style="background-color: var(--color-method); color: white;">Methode</span>';
} else if (hasValidProjectUrl) {
elements.badges.innerHTML += '<span class="badge badge-primary">CC24-Server</span>';
}
// Metadata - safe array handling
const metadataContainer = document.getElementById('tool-metadata');
if (tool.knowledgebase === true) {
elements.badges.innerHTML += '<span class="badge badge-error">📖</span>';
}
// Metadata
const domains = tool.domains || [];
const phases = tool.phases || [];
const domainsText = domains.length > 0 ? domains.join(', ') : 'Domain-agnostic';
const phasesText = phases.join(', ');
metadataContainer.innerHTML = `
<div style="display: grid; gap: 0.5rem;">
let metadataHTML = `<div style="display: grid; gap: 0.5rem;">`;
if (!isConcept) {
metadataHTML += `
<div><strong>Betriebssystem:</strong> ${(tool.platforms || []).join(', ')}</div>
<div><strong>Skill Level:</strong> ${tool.skillLevel}</div>
<div><strong>Lizenzmodell:</strong> ${tool.license}</div>
<div><strong>Deployment:</strong> ${tool.accessType}</div>
<div><strong>Einsatzgebiete:</strong> ${domainsText}</div>
<div><strong>Ermittlungsphasen:</strong> ${phasesText}</div>
</div>
`;
`;
} else {
metadataHTML += `<div><strong>Skill Level:</strong> ${tool.skillLevel}</div>`;
}
// Tags - safe array handling
const tagsContainer = document.getElementById('tool-tags');
metadataHTML += `
<div><strong>Einsatzgebiete:</strong> ${domainsText}</div>
<div><strong>Ermittlungsphasen:</strong> ${phasesText}</div>
</div>`;
elements.metadata.innerHTML = metadataHTML;
// Tags and Related Concepts
const tags = tool.tags || [];
tagsContainer.innerHTML = `
let tagsHTML = `
<div style="display: flex; flex-wrap: wrap; gap: 0.25rem;">
${tags.map(tag => `<span class="tag">${tag}</span>`).join('')}
</div>
`;
// Links
const linksContainer = document.getElementById('tool-links');
// Related Concepts section - only show in primary modal to avoid infinite loops
const relatedConcepts = tool.related_concepts || [];
if (relatedConcepts.length > 0 && modalType === 'primary') {
const conceptLinks = relatedConcepts.map(conceptName => {
const concept = toolsData.find(t => t.name === conceptName && t.type === 'concept');
if (concept) {
return `<button class="tag" style="cursor: pointer; background-color: var(--color-concept-bg); border: 1px solid var(--color-concept); color: var(--color-concept); transition: var(--transition-fast); margin: 0.125rem;"
onclick="event.stopPropagation(); window.showToolDetails('${conceptName}', 'secondary')"
onmouseover="this.style.backgroundColor='var(--color-concept)'; this.style.color='white';"
onmouseout="this.style.backgroundColor='var(--color-concept-bg)'; this.style.color='var(--color-concept)';">
${conceptName}
</button>`;
}
return `<span class="tag" style="background-color: var(--color-bg-tertiary); color: var(--color-text-secondary); margin: 0.125rem;">${conceptName}</span>`;
}).join('');
tagsHTML += `
<div style="margin-top: 1rem;">
<strong style="display: block; margin-bottom: 0.5rem; color: var(--color-text);">Verwandte Konzepte:</strong>
<div style="display: flex; flex-wrap: wrap; gap: 0.25rem;">
${conceptLinks}
</div>
</div>
`;
}
elements.tags.innerHTML = tagsHTML;
// Links
let linksHTML = '';
if (isMethod) {
// For methods, show link to the method description/documentation
if (isConcept) {
linksHTML += `
<a href="${tool.url}" target="_blank" rel="noopener noreferrer" class="btn btn-primary" style="width: 100%; background-color: var(--color-concept); border-color: var(--color-concept);">
Mehr erfahren
</a>
`;
} else if (isMethod) {
linksHTML += `
<a href="${tool.projectUrl || tool.url}" target="_blank" rel="noopener noreferrer" class="btn btn-primary" style="width: 100%; background-color: var(--color-method); border-color: var(--color-method);">
Zur Methode
@@ -315,7 +403,7 @@ domains.forEach((domain: any) => {
linksHTML += `
<div style="display: flex; gap: 0.5rem;">
<a href="${tool.url}" target="_blank" rel="noopener noreferrer" class="btn btn-secondary" style="flex: 1;">
Software-Homepage
Homepage
</a>
<a href="${tool.projectUrl}" target="_blank" rel="noopener noreferrer" class="btn btn-primary" style="flex: 1;">
Zugreifen
@@ -346,19 +434,60 @@ domains.forEach((domain: any) => {
`;
}
linksContainer.innerHTML = linksHTML;
elements.links.innerHTML = linksHTML;
// Show modal
document.getElementById('modal-overlay').classList.add('active');
document.getElementById('tool-details').classList.add('active');
};
window.hideToolDetails = function() {
document.getElementById('modal-overlay').classList.remove('active');
document.getElementById('tool-details').classList.remove('active');
// Show modals and update layout
const overlay = document.getElementById('modal-overlay');
const primaryModal = document.getElementById('tool-details-primary');
const secondaryModal = document.getElementById('tool-details-secondary');
if (overlay) overlay.classList.add('active');
if (modalType === 'primary' && primaryModal) primaryModal.classList.add('active');
if (modalType === 'secondary' && secondaryModal) secondaryModal.classList.add('active');
// Check if both modals are now active
const primaryActive = primaryModal && primaryModal.classList.contains('active');
const secondaryActive = secondaryModal && secondaryModal.classList.contains('active');
if (primaryActive && secondaryActive) {
document.body.classList.add('modals-side-by-side');
}
};
// Listen for view changes to trigger highlighting
window.hideToolDetails = function(modalType = 'both') {
const overlay = document.getElementById('modal-overlay');
const primaryModal = document.getElementById('tool-details-primary');
const secondaryModal = document.getElementById('tool-details-secondary');
if (modalType === 'both' || modalType === 'all') {
if (primaryModal) primaryModal.classList.remove('active');
if (secondaryModal) secondaryModal.classList.remove('active');
if (overlay) overlay.classList.remove('active');
document.body.classList.remove('modals-side-by-side');
} else if (modalType === 'primary' && primaryModal) {
primaryModal.classList.remove('active');
} else if (modalType === 'secondary' && secondaryModal) {
secondaryModal.classList.remove('active');
}
// Check if any modal is still active
const primaryActive = primaryModal && primaryModal.classList.contains('active');
const secondaryActive = secondaryModal && secondaryModal.classList.contains('active');
if (!primaryActive && !secondaryActive) {
if (overlay) overlay.classList.remove('active');
document.body.classList.remove('modals-side-by-side');
} else if (primaryActive !== secondaryActive) {
// Only one modal left - remove side-by-side layout
document.body.classList.remove('modals-side-by-side');
}
};
window.hideAllToolDetails = function() {
window.hideToolDetails('both');
};
// Keep existing event listeners
window.addEventListener('viewChanged', (event) => {
const view = event.detail;
if (view === 'matrix') {
@@ -366,7 +495,6 @@ domains.forEach((domain: any) => {
}
});
// Listen for filter changes to update highlighting
window.addEventListener('toolsFiltered', (event) => {
const currentView = document.querySelector('.view-toggle.active')?.getAttribute('data-view');
if (currentView === 'matrix') {
@@ -374,7 +502,6 @@ domains.forEach((domain: any) => {
}
});
// Update matrix on filter change
window.addEventListener('toolsFiltered', (event) => {
const filtered = event.detail;
const currentView = document.querySelector('.view-toggle.active')?.getAttribute('data-view');
@@ -382,33 +509,24 @@ domains.forEach((domain: any) => {
if (currentView === 'matrix') {
const selectedPhase = getSelectedPhase();
// Get all domain-agnostic phase IDs
const domainAgnosticPhaseIds = domainAgnosticSoftware.map(section => section.id);
// Check if selected phase is a domain-agnostic phase
const isDomainAgnosticPhase = domainAgnosticPhaseIds.includes(selectedPhase);
// Handle domain-agnostic sections
domainAgnosticSoftware.forEach(sectionData => {
const section = document.getElementById(`domain-agnostic-section-${sectionData.id}`);
const container = document.getElementById(`domain-agnostic-tools-${sectionData.id}`);
if (!section || !container) return;
});
if (!isDomainAgnosticPhase) {
// Show matrix for regular phases
document.getElementById('dfir-matrix-section').style.display = 'block';
// Clear and update matrix cells with ALL tools (based on domains × phases)
document.querySelectorAll('.matrix-cell').forEach(cell => {
cell.innerHTML = '';
});
// Re-populate with filtered tools based on domains × phases
filtered.forEach(tool => {
// Skip concepts - they don't belong in matrix
if (tool.type === 'concept') {
return;
}
@@ -428,8 +546,8 @@ domains.forEach((domain: any) => {
if (cell) {
const chip = document.createElement('span');
const chipClass = isMethod ? 'tool-chip-method' :
hasValidProjectUrl ? 'tool-chip-hosted' :
tool.license !== 'Proprietary' ? 'tool-chip-oss' : '';
hasValidProjectUrl ? 'tool-chip-hosted' :
tool.license !== 'Proprietary' ? 'tool-chip-oss' : '';
chip.className = `tool-chip ${chipClass}`;
chip.setAttribute('title', `${tool.name}${tool.knowledgebase === true ? ' (KB verfügbar)' : ''}`);
chip.innerHTML = `${tool.name}${tool.knowledgebase === true ? '<span style="margin-left: 0.25rem; font-size: 0.6875rem;">📖</span>' : ''}`;