| |
| |
| |
|
|
| document.addEventListener('DOMContentLoaded', () => { |
|
|
| |
| 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(); |
| } |
|
|
| |
| 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); |
| }); |
| } |
|
|
| |
| 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')) { |
| |
| } |
| }); |
| } |
|
|
| |
| 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 = ` |
| <td class="px-6 py-4 font-mono text-slate-500 text-xs">#${job.job_id.toUpperCase()}</td> |
| <td class="px-6 py-4 font-medium text-slate-200">${job.jar_name}</td> |
| <td class="px-6 py-4"> |
| <span class="bg-slate-800 text-slate-300 text-[10px] font-bold px-2 py-1 rounded border border-slate-700">${job.mc_version}</span> |
| </td> |
| <td class="px-6 py-4">${statusBadge(job.status)}</td> |
| <td class="px-6 py-4 text-xs">${timeAgo(job.created_at)}</td> |
| <td class="px-6 py-4 text-right"> |
| <div class="flex justify-end gap-2 opacity-0 group-hover:opacity-100 transition-opacity"> |
| ${job.status === 'done' ? '<button class="p-1 text-slate-500 hover:text-emerald-400 transition-colors" onclick="window.location.href=\'results.html?job=' + job.job_id + '\'"><span class="material-symbols-outlined text-[18px]">download</span></button>' : ''} |
| ${job.status === 'running' ? '<button class="p-1 text-slate-500 hover:text-amber-400 transition-colors" onclick="window.location.href=\'job.html?job=' + job.job_id + '\'"><span class="material-symbols-outlined text-[18px]">visibility</span></button>' : ''} |
| <button class="p-1 text-slate-500 hover:text-red-400 transition-colors" onclick="this.closest(\'tr\').remove()"><span class="material-symbols-outlined text-[18px]">delete</span></button> |
| </div> |
| </td> |
| `; |
| 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); |
| }); |
|
|
| |
| 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; |
|
|
| |
| 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'); |
| } |
| } |
| } |
|
|
| |
| function initJobDetail() { |
| 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-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; }); |
|
|
| |
| 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 `<div class="flex flex-col items-center gap-2 flex-1"> |
| <div class="w-8 h-8 rounded-full border-2 ${dotCls} ${cls} flex items-center justify-center text-xs font-bold">${i < activeIndex || job.status === 'done' ? '✓' : i + 1}</div> |
| <span class="text-xs ${cls} font-label">${step}</span> |
| </div>${i < steps.length - 1 ? '<div class="flex-1 h-0.5 mt-4 ' + (i < activeIndex ? 'bg-emerald-500' : 'bg-slate-700') + '"></div>' : ''}`; |
| }).join(''); |
| }); |
|
|
| |
| 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 = ` |
| <td class="px-4 py-3 text-xs">${build.iteration}</td> |
| <td class="px-4 py-3 ${statusColor} font-bold text-sm">${statusIcon} ${build.status}</td> |
| <td class="px-4 py-3 text-xs text-slate-500">${build.time}</td> |
| <td class="px-4 py-3"><button class="text-xs text-indigo-400 hover:text-indigo-300 transition-colors" onclick="showBuildLog('${bid}')">View Log</button></td> |
| `; |
| buildTbody.appendChild(tr); |
| }); |
| } |
| } |
|
|
| |
| 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 `<div class="${cls}">${line}</div>`; |
| }).join(''); |
| terminal.scrollTop = 0; |
| |
| terminal.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); |
| }; |
|
|
| |
| 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); |
|
|
| |
| const reportEl = document.querySelector('[data-conversion-report]'); |
| if (reportEl && jobId === 'cb-892a') { |
| reportEl.innerHTML = renderMarkdown(DEMO.conversionReport); |
| } |
| } |
|
|
| |
| function renderMarkdown(text) { |
| return text |
| .replace(/^### (.+)$/gm, '<h3 class="text-lg font-bold text-slate-200 mt-4 mb-2">$1</h3>') |
| .replace(/^## (.+)$/gm, '<h2 class="text-xl font-bold text-slate-100 mt-6 mb-3">$1</h2>') |
| .replace(/^# (.+)$/gm, '<h1 class="text-2xl font-bold text-white mt-8 mb-4">$1</h1>') |
| .replace(/^- (.+)$/gm, '<div class="flex gap-2 text-sm text-slate-300 pl-4"><span class="text-slate-500">•</span><span>$1</span></div>') |
| .replace(/⚠️/g, '<span class="text-amber-400">⚠️</span>') |
| .replace(/✅/g, '<span class="text-emerald-400">✅</span>') |
| .replace(/✗/g, '<span class="text-red-400">✗</span>') |
| .replace(/\*\*(.+?)\*\*/g, '<strong class="text-slate-200">$1</strong>') |
| .replace(/\n\n/g, '<br><br>') |
| .replace(/\n/g, '<br>'); |
| } |
|
|
| |
| 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); |
| } |
| }); |
|
|
| |
| dropzone.addEventListener('click', (e) => { |
| |
| |
| 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'); |
| } |
| } |
|
|
| |
| document.addEventListener('click', (e) => { |
| const convertBtn = e.target.closest('[data-convert-btn]'); |
| if (convertBtn && !convertBtn.disabled) { |
| convertBtn.innerHTML = '<span class="material-symbols-outlined animate-spin">progress_activity</span> Starting...'; |
| convertBtn.disabled = true; |
| setTimeout(() => { |
| window.location.href = 'job.html?job=cb-892b'; |
| }, 1500); |
| } |
|
|
| |
| const downloadBtn = e.target.closest('[data-download-plugin], [data-download-rp], [data-download-dp]'); |
| if (downloadBtn) { |
| const original = downloadBtn.innerHTML; |
| downloadBtn.innerHTML = '<span class="material-symbols-outlined">check</span> Downloaded!'; |
| downloadBtn.classList.add('opacity-60'); |
| setTimeout(() => { |
| downloadBtn.innerHTML = original; |
| downloadBtn.classList.remove('opacity-60'); |
| }, 2000); |
| } |
|
|
| |
| const signInBtn = e.target.closest('[data-sign-in]'); |
| if (signInBtn) { |
| signInBtn.innerHTML = '<span class="material-symbols-outlined animate-spin">progress_activity</span> Signing in...'; |
| setTimeout(() => { |
| window.location.href = 'dashboard.html'; |
| }, 800); |
| } |
|
|
| |
| const startBtn = e.target.closest('[data-start-converting]'); |
| if (startBtn) { |
| window.location.href = 'upload.html'; |
| } |
|
|
| |
| const anotherBtn = e.target.closest('[data-convert-another]'); |
| if (anotherBtn) { |
| window.location.href = 'upload.html'; |
| } |
|
|
| |
| const cancelBtn = e.target.closest('[data-cancel-job]'); |
| if (cancelBtn) { |
| cancelBtn.textContent = 'Cancelled'; |
| cancelBtn.classList.add('opacity-50'); |
| cancelBtn.disabled = true; |
| } |
| }); |
|
|
| |
| 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 || ''; |
|
|
| |
| if (selectedValue && valueEl) { |
| valueEl.textContent = 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); |
| 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(); |
| }); |
|
|
| |
| document.addEventListener('click', (e) => { |
| if (!selector.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 = selectedValue; |
| trigger.classList.add('has-value'); |
|
|
| |
| options.forEach(o => o.classList.remove('selected')); |
| opt.classList.add('selected'); |
|
|
| |
| setTimeout(() => toggle(true), 120); |
|
|
| |
| updateConvertButton(); |
| }); |
| }); |
|
|
| |
| 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; |
| }); |
| |
| 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; |
| |
| const hasFile = document.querySelector('[data-dropzone].has-file'); |
| if (hasFile && selectedValue) { |
| btn.disabled = false; |
| btn.classList.remove('opacity-40', 'cursor-not-allowed'); |
| } |
| } |
| }); |
| } |
|
|
| |
| function initSettingsSelectors() { |
| |
| 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); |
| }); |
| }); |
| } |
| } |
|
|
| |
| function initTerminal() { |
| const terminal = document.querySelector('[data-terminal]'); |
| if (!terminal) return; |
| terminal.scrollTop = terminal.scrollHeight; |
| } |
|
|
| |
| 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 = '<span class="material-symbols-outlined text-emerald-400">check</span>'; |
| setTimeout(() => btn.innerHTML = original, 1500); |
| }); |
| } |
| }); |
| }); |
|
|
| |
| 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 = '<span class="material-symbols-outlined text-emerald-400 text-[16px]">check</span> Copied!'; |
| btn.classList.add('text-emerald-400'); |
| setTimeout(() => { |
| btn.innerHTML = original; |
| btn.classList.remove('text-emerald-400'); |
| }, 2000); |
| }); |
| } |
| }); |
| }); |
| }); |
|
|