/* CraftBridge — Demo interaction handler * Wires up all buttons, nav, dropzone, tables etc. with fake data. */ document.addEventListener('DOMContentLoaded', () => { // --- Load demo data --- const demoScript = document.createElement('script'); demoScript.src = 'demo-data.js'; demoScript.onload = initDemo; document.head.appendChild(demoScript); function initDemo() { initAnimations(); initDropzone(); initTerminal(); initNav(); initDashboard(); initJobDetail(); initResults(); initMcSelector(); initSettingsSelectors(); } // --- Scroll animations --- function initAnimations() { const observer = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { entry.target.classList.add('visible'); observer.unobserve(entry.target); } }); }, { threshold: 0.1, rootMargin: '0px 0px -40px 0px' }); document.querySelectorAll('.reveal, .reveal-scale, .stagger').forEach(el => { observer.observe(el); }); } // --- Navigation --- function initNav() { const path = window.location.pathname; document.querySelectorAll('aside nav a, .sidebar nav a').forEach(a => { const href = a.getAttribute('href') || ''; a.classList.remove('active'); if (!a.classList.contains('text-emerald-400') && !a.classList.contains('text-emerald-700') && !a.classList.contains('dark\\:text-emerald-400')) { // no-op } }); } // --- Dashboard: populate job table --- function initDashboard() { const tbody = document.querySelector('[data-job-table]'); if (!tbody) return; const jobs = getJobs(); tbody.innerHTML = ''; jobs.forEach(job => { const tr = document.createElement('tr'); tr.className = 'hover:bg-slate-800/20 transition-colors group cursor-pointer'; tr.innerHTML = ` #${job.job_id.toUpperCase()} ${job.jar_name} ${job.mc_version} ${statusBadge(job.status)} ${timeAgo(job.created_at)}
${job.status === 'done' ? '' : ''} ${job.status === 'running' ? '' : ''}
`; tr.addEventListener('click', (e) => { if (e.target.closest('button')) return; if (job.status === 'done') window.location.href = `results.html?job=${job.job_id}`; else if (job.status === 'running') window.location.href = `job.html?job=${job.job_id}`; else window.location.href = `job.html?job=${job.job_id}`; }); tbody.appendChild(tr); }); // Update stat numbers const totalEl = document.querySelector('[data-stat-total]'); const successEl = document.querySelector('[data-stat-success]'); const failedEl = document.querySelector('[data-stat-failed]'); if (totalEl) totalEl.textContent = jobs.length; if (successEl) successEl.textContent = jobs.filter(j => j.status === 'done').length; if (failedEl) failedEl.textContent = jobs.filter(j => j.status === 'failed').length; // Show empty state if no jobs const emptyState = document.querySelector('[data-empty-state]'); const tableEl = tbody.closest('table'); if (emptyState) { if (jobs.length === 0) { emptyState.classList.remove('hidden'); if (tableEl) tableEl.classList.add('hidden'); } else { emptyState.classList.add('hidden'); if (tableEl) tableEl.classList.remove('hidden'); } } } // --- Job detail page --- function initJobDetail() { const params = new URLSearchParams(window.location.search); const jobId = params.get('job'); if (!jobId) return; const job = getJob(jobId); if (!job) return; // Update page content with job data document.querySelectorAll('[data-job-id]').forEach(el => el.textContent = `#${jobId.toUpperCase()}`); document.querySelectorAll('[data-job-name]').forEach(el => el.textContent = job.jar_name); document.querySelectorAll('[data-job-version]').forEach(el => el.textContent = job.mc_version); document.querySelectorAll('[data-job-status]').forEach(el => { el.innerHTML = statusBadge(job.status); }); document.querySelectorAll('[data-job-iteration]').forEach(el => { el.textContent = job.current_iteration; }); // Progress steps const steps = ['Uploaded', 'Analyzing', 'Building', 'Testing', 'Complete']; const activeIndex = job.status === 'done' ? 4 : job.status === 'failed' ? 2 : job.status === 'running' ? 2 : 0; document.querySelectorAll('[data-progress-steps]').forEach(container => { container.innerHTML = steps.map((step, i) => { let cls = 'text-slate-500 border-slate-700'; let dotCls = 'bg-slate-700'; if (i < activeIndex) { cls = 'text-emerald-400 border-emerald-500'; dotCls = 'bg-emerald-500'; } if (i === activeIndex && job.status === 'running') { cls = 'text-amber-400 border-amber-500'; dotCls = 'bg-amber-400 animate-pulse'; } if (i === activeIndex && job.status === 'done') { cls = 'text-emerald-400 border-emerald-500'; dotCls = 'bg-emerald-500'; } return `
${i < activeIndex || job.status === 'done' ? '✓' : i + 1}
${step}
${i < steps.length - 1 ? '
' : ''}`; }).join(''); }); // Build iterations table const buildTbody = document.querySelector('[data-build-table]'); if (buildTbody && job.build_run_ids) { buildTbody.innerHTML = ''; job.build_run_ids.forEach((bid, i) => { const build = DEMO.builds[bid]; if (!build) return; const tr = document.createElement('tr'); tr.className = 'hover:bg-slate-800/20 transition-colors'; const statusIcon = build.status === 'passed' ? '✓' : build.status === 'failed' ? '✗' : '⏳'; const statusColor = build.status === 'passed' ? 'text-emerald-400' : build.status === 'failed' ? 'text-red-400' : 'text-amber-400'; tr.innerHTML = ` ${build.iteration} ${statusIcon} ${build.status} ${build.time} `; buildTbody.appendChild(tr); }); } } // --- Show build log in terminal --- window.showBuildLog = function(buildId) { const build = DEMO.builds[buildId]; if (!build) return; const terminal = document.querySelector('[data-terminal]'); if (!terminal) return; terminal.innerHTML = build.log.split('\n').map(line => { let cls = 'text-slate-400'; if (line.includes('[ERROR]')) cls = 'text-red-400'; else if (line.includes('[WARN]')) cls = 'text-amber-400'; else if (line.includes('[INFO]') && line.includes('SUCCESS')) cls = 'text-emerald-400'; else if (line.includes('[INFO]')) cls = 'text-slate-500'; else if (line.includes('>')) cls = 'text-emerald-400'; return `
${line}
`; }).join(''); terminal.scrollTop = 0; // Highlight the terminal section terminal.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); }; // --- Results page --- function initResults() { const params = new URLSearchParams(window.location.search); const jobId = params.get('job'); if (!jobId) return; const job = getJob(jobId); if (!job) return; document.querySelectorAll('[data-result-name]').forEach(el => el.textContent = job.jar_name); document.querySelectorAll('[data-result-version]').forEach(el => el.textContent = job.mc_version); // Populate report const reportEl = document.querySelector('[data-conversion-report]'); if (reportEl && jobId === 'cb-892a') { reportEl.innerHTML = renderMarkdown(DEMO.conversionReport); } } // --- Simple markdown renderer --- function renderMarkdown(text) { return text .replace(/^### (.+)$/gm, '

$1

') .replace(/^## (.+)$/gm, '

$1

') .replace(/^# (.+)$/gm, '

$1

') .replace(/^- (.+)$/gm, '
$1
') .replace(/⚠️/g, '⚠️') .replace(/✅/g, '') .replace(/✗/g, '') .replace(/\*\*(.+?)\*\*/g, '$1') .replace(/\n\n/g, '

') .replace(/\n/g, '
'); } // --- Dropzone --- function initDropzone() { const dropzone = document.querySelector('[data-dropzone]'); if (!dropzone) return; const label = dropzone.querySelector('[data-dropzone-label]'); ['dragenter', 'dragover'].forEach(evt => { dropzone.addEventListener(evt, (e) => { e.preventDefault(); dropzone.classList.add('dropzone-active', 'border-emerald-500/50'); }); }); ['dragleave', 'drop'].forEach(evt => { dropzone.addEventListener(evt, (e) => { e.preventDefault(); dropzone.classList.remove('dropzone-active', 'border-emerald-500/50'); }); }); dropzone.addEventListener('drop', (e) => { const files = e.dataTransfer.files; if (files.length > 0) { if (label) label.textContent = files[0].name; dropzone.classList.add('has-file', 'border-emerald-500'); enableConvertBtn(files[0].name); } }); // Click to browse (only on the dropzone area, not the Browse button) dropzone.addEventListener('click', (e) => { // If clicking the Browse button, let it handle the file picker // Otherwise open the file picker from the dropzone const input = document.createElement('input'); input.type = 'file'; input.accept = '.jar'; input.onchange = () => { if (input.files.length > 0) { if (label) label.textContent = input.files[0].name; dropzone.classList.add('has-file', 'border-emerald-500'); enableConvertBtn(input.files[0].name); } }; input.click(); }); } function enableConvertBtn(filename) { const btn = document.querySelector('[data-convert-btn]'); if (btn) { btn.disabled = false; btn.classList.remove('opacity-40', 'cursor-not-allowed'); } } // --- Convert button --- document.addEventListener('click', (e) => { const convertBtn = e.target.closest('[data-convert-btn]'); if (convertBtn && !convertBtn.disabled) { convertBtn.innerHTML = 'progress_activity Starting...'; convertBtn.disabled = true; setTimeout(() => { window.location.href = 'job.html?job=cb-892b'; }, 1500); } // Download buttons on results page const downloadBtn = e.target.closest('[data-download-plugin], [data-download-rp], [data-download-dp]'); if (downloadBtn) { const original = downloadBtn.innerHTML; downloadBtn.innerHTML = 'check Downloaded!'; downloadBtn.classList.add('opacity-60'); setTimeout(() => { downloadBtn.innerHTML = original; downloadBtn.classList.remove('opacity-60'); }, 2000); } // Sign in button → go to dashboard const signInBtn = e.target.closest('[data-sign-in]'); if (signInBtn) { signInBtn.innerHTML = 'progress_activity Signing in...'; setTimeout(() => { window.location.href = 'dashboard.html'; }, 800); } // Start Converting CTA const startBtn = e.target.closest('[data-start-converting]'); if (startBtn) { window.location.href = 'upload.html'; } // Convert Another const anotherBtn = e.target.closest('[data-convert-another]'); if (anotherBtn) { window.location.href = 'upload.html'; } // Cancel job const cancelBtn = e.target.closest('[data-cancel-job]'); if (cancelBtn) { cancelBtn.textContent = 'Cancelled'; cancelBtn.classList.add('opacity-50'); cancelBtn.disabled = true; } }); // --- Custom MC Version Selector (supports multiple on page) --- function initMcSelector() { document.querySelectorAll('[data-mc-selector]').forEach(selector => { const trigger = selector.querySelector('[data-mc-trigger]'); const panel = selector.querySelector('[data-mc-panel]'); const valueEl = selector.querySelector('[data-mc-value]'); const searchInput = selector.querySelector('[data-mc-search]'); const options = selector.querySelectorAll('.mc-selector-option'); const groups = selector.querySelectorAll('[data-mc-list] .mc-selector-group'); let isOpen = false; let selectedValue = selector.dataset.defaultValue || ''; // Set initial value if provided if (selectedValue && valueEl) { valueEl.textContent = selectedValue; trigger.classList.add('has-value'); options.forEach(o => { if (o.dataset.value === selectedValue) o.classList.add('selected'); }); } // Toggle open/close function toggle(forceClose = false) { isOpen = forceClose ? false : !isOpen; trigger.classList.toggle('open', isOpen); panel.classList.toggle('open', isOpen); selector.classList.toggle('selector-open', isOpen); if (isOpen && searchInput) { setTimeout(() => searchInput.focus(), 150); } if (!isOpen && searchInput) { searchInput.value = ''; filterOptions(''); } } trigger.addEventListener('click', (e) => { e.stopPropagation(); toggle(); }); // Close on outside click document.addEventListener('click', (e) => { if (!selector.contains(e.target)) { toggle(true); } }); // Close on Escape document.addEventListener('keydown', (e) => { if (e.key === 'Escape' && isOpen) { toggle(true); trigger.focus(); } }); // Select an option options.forEach(opt => { opt.addEventListener('click', () => { selectedValue = opt.dataset.value; if (valueEl) valueEl.textContent = selectedValue; trigger.classList.add('has-value'); // Mark selected options.forEach(o => o.classList.remove('selected')); opt.classList.add('selected'); // Smooth close setTimeout(() => toggle(true), 120); // Enable convert button if file is also selected updateConvertButton(); }); }); // Search/filter if (searchInput) { searchInput.addEventListener('input', () => { filterOptions(searchInput.value.toLowerCase()); }); } function filterOptions(query) { groups.forEach(group => { const groupOptions = group.querySelectorAll('.mc-selector-option'); let anyVisible = false; groupOptions.forEach(opt => { const name = opt.querySelector('.mc-version-name'); const matches = !query || (name && name.textContent.toLowerCase().includes(query)); opt.classList.toggle('hidden', !matches); if (matches) anyVisible = true; }); // Hide group label if no options visible const label = group.querySelector('.mc-selector-group-label'); if (label) label.classList.toggle('hidden', !anyVisible); }); } function updateConvertButton() { const btn = document.querySelector('[data-convert-btn]'); if (!btn) return; // Enable if both file and version are selected const hasFile = document.querySelector('[data-dropzone].has-file'); if (hasFile && selectedValue) { btn.disabled = false; btn.classList.remove('opacity-40', 'cursor-not-allowed'); } } }); } // --- Settings page selectors --- function initSettingsSelectors() { // Iteration selector const iterSelector = document.querySelector('[data-iter-selector]'); if (iterSelector) { const trigger = iterSelector.querySelector('[data-mc-trigger]'); const panel = iterSelector.querySelector('[data-mc-panel]'); const valueEl = iterSelector.querySelector('[data-mc-value]'); const options = iterSelector.querySelectorAll('.mc-selector-option'); let isOpen = false; let selectedValue = iterSelector.dataset.defaultValue || '5'; if (selectedValue && valueEl) { valueEl.textContent = selectedValue === '0' ? '∞' : selectedValue; trigger.classList.add('has-value'); options.forEach(o => { if (o.dataset.value === selectedValue) o.classList.add('selected'); }); } function toggle(forceClose = false) { isOpen = forceClose ? false : !isOpen; trigger.classList.toggle('open', isOpen); panel.classList.toggle('open', isOpen); iterSelector.classList.toggle('selector-open', isOpen); } trigger.addEventListener('click', (e) => { e.stopPropagation(); toggle(); }); document.addEventListener('click', (e) => { if (!iterSelector.contains(e.target)) toggle(true); }); document.addEventListener('keydown', (e) => { if (e.key === 'Escape' && isOpen) { toggle(true); trigger.focus(); } }); options.forEach(opt => { opt.addEventListener('click', () => { selectedValue = opt.dataset.value; if (valueEl) valueEl.textContent = opt.dataset.display || selectedValue; trigger.classList.add('has-value'); options.forEach(o => o.classList.remove('selected')); opt.classList.add('selected'); setTimeout(() => toggle(true), 120); }); }); } } // --- Terminal auto-scroll --- function initTerminal() { const terminal = document.querySelector('[data-terminal]'); if (!terminal) return; terminal.scrollTop = terminal.scrollHeight; } // --- Copy button --- document.querySelectorAll('[data-copy]').forEach(btn => { btn.addEventListener('click', () => { const target = document.querySelector(btn.dataset.copy); if (target) { navigator.clipboard.writeText(target.textContent).then(() => { const original = btn.innerHTML; btn.innerHTML = 'check'; setTimeout(() => btn.innerHTML = original, 1500); }); } }); }); // --- Copy report as markdown --- document.querySelectorAll('[data-copy-report]').forEach(btn => { btn.addEventListener('click', () => { const reportEl = document.querySelector('[data-conversion-report]'); if (reportEl) { const text = reportEl.innerText || reportEl.textContent; navigator.clipboard.writeText(text).then(() => { const original = btn.innerHTML; btn.innerHTML = 'check Copied!'; btn.classList.add('text-emerald-400'); setTimeout(() => { btn.innerHTML = original; btn.classList.remove('text-emerald-400'); }, 2000); }); } }); }); });