overcuriousity 64d5e75045 progress
2025-07-13 22:18:53 +02:00

244 lines
8.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// File: ./src/js/modal.js
// Tool detail modal functionality
(function() {
'use strict';
let modal;
let modalContent;
let currentTool = null;
// Initialize modal system
function init() {
modal = document.getElementById('tool-modal');
modalContent = document.getElementById('tool-modal-content');
if (!modal || !modalContent) {
console.warn('Modal elements not found');
return;
}
// Add event listeners
setupEventListeners();
}
// Set up event listeners for modal
function setupEventListeners() {
// Click outside modal to close
modal.addEventListener('click', (event) => {
if (event.target === modal) {
closeModal();
}
});
// Escape key to close modal
document.addEventListener('keydown', (event) => {
if (event.key === 'Escape' && !modal.classList.contains('hidden')) {
closeModal();
}
});
// Prevent body scroll when modal is open
modal.addEventListener('scroll', (event) => {
event.stopPropagation();
});
}
// Show tool detail modal
function showToolModal(tool) {
if (!tool || !modal || !modalContent) return;
currentTool = tool;
// Generate modal content
modalContent.innerHTML = generateModalContent(tool);
// Add close button handler
const closeButton = modalContent.querySelector('.modal-close');
if (closeButton) {
closeButton.addEventListener('click', closeModal);
}
// Add external link handler
const externalLink = modalContent.querySelector('.external-link');
if (externalLink) {
externalLink.addEventListener('click', (event) => {
// Let the link work normally, but track the click
trackToolClick(tool);
});
}
// Show modal
modal.classList.remove('hidden');
document.body.style.overflow = 'hidden';
// Focus trap
trapFocus();
}
// Close modal
function closeModal() {
if (!modal) return;
modal.classList.add('hidden');
document.body.style.overflow = '';
currentTool = null;
// Return focus to the tool card that was clicked
const activeToolCard = document.querySelector('.tool-card:focus');
if (activeToolCard) {
activeToolCard.focus();
}
}
// Generate modal HTML content
function generateModalContent(tool) {
const typeLabel = tool.type === 'SaaS' ?
'<span class="tag tag-purple">SaaS</span>' :
'<span class="tag tag-green">FOSS</span>';
const domains = (tool.domains || []).map(domain =>
`<span class="block tag tag-green mb-2">${domain}</span>`
).join('');
const phases = (tool.phases || []).map(phase =>
`<span class="block tag tag-blue mb-2">${phase}</span>`
).join('');
const tags = (tool.tags || []).map(tag =>
`<span class="tag tag-gray mr-2 mb-2">${tag}</span>`
).join('');
const platforms = (tool.platforms || []).join(', ');
// Handle service URL for self-hosted services
const serviceInfo = tool.selfHosted && tool.serviceUrl ?
`<div class="mb-4 p-3 bg-blue-50 dark:bg-blue-900 rounded-lg">
<p class="text-sm text-blue-800 dark:text-blue-200">
<strong>Lab Instance:</strong>
<a href="${tool.serviceUrl}" target="_blank" rel="noopener noreferrer"
class="underline hover:no-underline">${tool.serviceUrl}</a>
</p>
</div>` : '';
return `
<div class="flex justify-between items-start mb-4">
<div>
<h2 class="text-2xl font-bold text-gray-900 dark:text-gray-100">${tool.name}</h2>
<div class="flex items-center gap-2 mt-1">
${typeLabel}
<span class="text-sm text-gray-500 dark:text-gray-400">${tool.skillLevel}</span>
</div>
</div>
<button
class="modal-close text-gray-400 dark:text-gray-500 hover:text-gray-600 dark:hover:text-gray-300 text-2xl"
aria-label="Schließen"
>
×
</button>
</div>
<p class="text-gray-600 dark:text-gray-300 mb-6">${tool.description}</p>
${serviceInfo}
<div class="grid grid-cols-2 gap-6 mb-6">
<div>
<h3 class="font-semibold text-gray-900 dark:text-gray-100 mb-3">Bereiche</h3>
<div class="space-y-2">
${domains || '<span class="text-gray-500 dark:text-gray-400 text-sm">Keine angegeben</span>'}
</div>
</div>
<div>
<h3 class="font-semibold text-gray-900 dark:text-gray-100 mb-3">Phasen</h3>
<div class="space-y-2">
${phases || '<span class="text-gray-500 dark:text-gray-400 text-sm">Keine angegeben</span>'}
</div>
</div>
</div>
<div class="mb-6">
<h3 class="font-semibold text-gray-900 dark:text-gray-100 mb-3">Tags</h3>
<div class="flex flex-wrap">
${tags || '<span class="text-gray-500 dark:text-gray-400 text-sm">Keine Tags</span>'}
</div>
</div>
<div class="flex justify-between items-center pt-4 border-t border-gray-200 dark:border-gray-700">
<div class="text-sm text-gray-600 dark:text-gray-400">
<div>Zugriff: <span class="font-medium">${tool.accessType}</span></div>
<div>Plattformen: <span class="font-medium">${platforms}</span></div>
</div>
<a
href="${tool.url}"
target="_blank"
rel="noopener noreferrer"
class="external-link flex items-center gap-2 px-6 py-2 bg-gray-600 text-white rounded-lg hover:bg-gray-700 transition-colors"
>
Projekt öffnen
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"/>
</svg>
</a>
</div>
`;
}
// Simple focus trap for modal
function trapFocus() {
const focusableElements = modal.querySelectorAll(
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
);
if (focusableElements.length === 0) return;
const firstElement = focusableElements[0];
const lastElement = focusableElements[focusableElements.length - 1];
// Focus first element
firstElement.focus();
// Handle tab key
modal.addEventListener('keydown', (event) => {
if (event.key !== 'Tab') return;
if (event.shiftKey) {
// Shift + Tab
if (document.activeElement === firstElement) {
event.preventDefault();
lastElement.focus();
}
} else {
// Tab
if (document.activeElement === lastElement) {
event.preventDefault();
firstElement.focus();
}
}
});
}
// Track tool clicks for analytics (placeholder)
function trackToolClick(tool) {
// This is where you'd send analytics data
console.log('Tool clicked:', tool.name, tool.url);
// Could send to analytics service:
// analytics.track('tool_clicked', {
// tool_name: tool.name,
// tool_type: tool.type,
// tool_url: tool.url
// });
}
// Export functions for use by other scripts
window.showToolModal = showToolModal;
window.closeToolModal = closeModal;
// Initialize when DOM is ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();