NanoBotAIAgent's picture
Upload folder using huggingface_hub
442ac33 verified
/* 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 = `
<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);
});
// 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 `<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('');
});
// 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 = `
<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);
});
}
}
// --- 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 `<div class="${cls}">${line}</div>`;
}).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, '<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>');
}
// --- 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 = '<span class="material-symbols-outlined animate-spin">progress_activity</span> 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 = '<span class="material-symbols-outlined">check</span> 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 = '<span class="material-symbols-outlined animate-spin">progress_activity</span> 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 = '<span class="material-symbols-outlined text-emerald-400">check</span>';
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 = '<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);
});
}
});
});
});