332 lines
9.5 KiB
JavaScript
332 lines
9.5 KiB
JavaScript
/**
|
|
* New Features Implementation
|
|
* - Filament/Resin Cost Calculator
|
|
* - Full-text Search Enhancement
|
|
* - License Management
|
|
* - Printer Integration (Bambu)
|
|
* - Dark/Light Theme Toggle
|
|
*/
|
|
|
|
/**
|
|
* Show Printer Settings Modal
|
|
*/
|
|
function showPrinterSettings() {
|
|
loadPrinters();
|
|
openModal('printerSettingsModal');
|
|
}
|
|
|
|
/**
|
|
* Load user's connected printers
|
|
*/
|
|
function loadPrinters() {
|
|
fetch(`${API_BASE}/printers/printers`, {
|
|
headers: { 'Authorization': `Bearer ${authToken}` }
|
|
})
|
|
.then(res => res.json())
|
|
.then(data => {
|
|
displayPrinters(data.printers || []);
|
|
})
|
|
.catch(err => console.error('Error loading printers:', err));
|
|
}
|
|
|
|
/**
|
|
* Display connected printers
|
|
*/
|
|
function displayPrinters(printers) {
|
|
const printerList = document.getElementById('printerList');
|
|
|
|
if (printers.length === 0) {
|
|
printerList.innerHTML = '<p class="text-muted">No printers connected yet.</p>';
|
|
return;
|
|
}
|
|
|
|
printerList.innerHTML = printers.map(printer => `
|
|
<div class="printer-item">
|
|
<h4>${printer.printer_name || 'Unnamed Printer'}</h4>
|
|
<p><strong>Type:</strong> ${printer.printer_type}</p>
|
|
<p><strong>Serial:</strong> ${printer.serial_number}</p>
|
|
<p><strong>Model:</strong> ${printer.model_name || 'N/A'}</p>
|
|
<div class="printer-actions">
|
|
<button class="btn btn-sm btn-secondary" onclick="refreshPrinterStatus(${printer.id})">
|
|
<i class="fas fa-sync"></i> Refresh
|
|
</button>
|
|
<button class="btn btn-sm btn-danger" onclick="removePrinter(${printer.id})">
|
|
<i class="fas fa-trash"></i> Remove
|
|
</button>
|
|
</div>
|
|
</div>
|
|
`).join('');
|
|
}
|
|
|
|
/**
|
|
* Add new Bambu printer
|
|
*/
|
|
function handleAddPrinter(event) {
|
|
event.preventDefault();
|
|
|
|
const printerName = document.getElementById('printerName').value;
|
|
const printerSerial = document.getElementById('printerSerial').value;
|
|
const printerModel = document.getElementById('printerModel').value;
|
|
const printerToken = document.getElementById('printerToken').value;
|
|
|
|
fetch(`${API_BASE}/printers/bambu/connect`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Authorization': `Bearer ${authToken}`
|
|
},
|
|
body: JSON.stringify({
|
|
printerName,
|
|
serialNumber: printerSerial,
|
|
modelName: printerModel,
|
|
accessToken: printerToken
|
|
})
|
|
})
|
|
.then(res => res.json())
|
|
.then(data => {
|
|
if (data.printerConnected) {
|
|
showNotification('Printer connected successfully!', 'success');
|
|
document.querySelector('#printerSettingsModal form').reset();
|
|
loadPrinters();
|
|
} else {
|
|
showNotification(data.error || 'Failed to connect printer', 'error');
|
|
}
|
|
})
|
|
.catch(err => {
|
|
showNotification('Error connecting printer: ' + err.message, 'error');
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Remove connected printer
|
|
*/
|
|
function removePrinter(printerId) {
|
|
if (!confirm('Are you sure you want to disconnect this printer?')) {
|
|
return;
|
|
}
|
|
|
|
fetch(`${API_BASE}/printers/printers/${printerId}`, {
|
|
method: 'DELETE',
|
|
headers: { 'Authorization': `Bearer ${authToken}` }
|
|
})
|
|
.then(res => res.json())
|
|
.then(data => {
|
|
showNotification('Printer disconnected', 'success');
|
|
loadPrinters();
|
|
})
|
|
.catch(err => showNotification('Error removing printer: ' + err.message, 'error'));
|
|
}
|
|
|
|
/**
|
|
* Refresh printer status
|
|
*/
|
|
function refreshPrinterStatus(printerId) {
|
|
fetch(`${API_BASE}/printers/bambu/${printerId}/status`, {
|
|
headers: { 'Authorization': `Bearer ${authToken}` }
|
|
})
|
|
.then(res => res.json())
|
|
.then(data => {
|
|
showNotification('Printer status: ' + JSON.stringify(data.data || data.error), 'info');
|
|
})
|
|
.catch(err => showNotification('Error fetching printer status', 'error'));
|
|
}
|
|
|
|
/**
|
|
* Show cost calculator modal
|
|
*/
|
|
function showCostCalculator() {
|
|
openModal('costCalculatorModal');
|
|
updateCostEstimate();
|
|
}
|
|
|
|
/**
|
|
* Update cost estimate for selected models
|
|
*/
|
|
function updateCostEstimate() {
|
|
const selectedModels = Array.from(document.querySelectorAll('.model-card.selected')).map(card => ({
|
|
id: parseInt(card.dataset.modelId),
|
|
name: card.querySelector('.model-name').textContent,
|
|
file_size: parseInt(card.dataset.fileSize)
|
|
}));
|
|
|
|
const materialType = document.getElementById('materialType')?.value || 'pla';
|
|
const costResults = document.getElementById('costResults');
|
|
|
|
if (selectedModels.length === 0) {
|
|
costResults.innerHTML = '<p class="text-muted">Select models from the grid to see cost estimates</p>';
|
|
return;
|
|
}
|
|
|
|
const modelIds = selectedModels.map(m => m.id);
|
|
|
|
fetch(`${API_BASE}/models/batch/cost`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Authorization': `Bearer ${authToken}`
|
|
},
|
|
body: JSON.stringify({ modelIds, materialType })
|
|
})
|
|
.then(res => res.json())
|
|
.then(data => {
|
|
displayCostResults(data);
|
|
})
|
|
.catch(err => {
|
|
costResults.innerHTML = `<p class="text-muted">Error calculating costs: ${err.message}</p>`;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Display cost calculation results
|
|
*/
|
|
function displayCostResults(data) {
|
|
const costResults = document.getElementById('costResults');
|
|
|
|
const cardsHtml = data.models.map(model => `
|
|
<div class="cost-card">
|
|
<h4 title="${model.name}">${model.name}</h4>
|
|
<div class="cost-detail">
|
|
<span>Weight:</span>
|
|
<span class="value">${model.weight.toFixed(1)}g</span>
|
|
</div>
|
|
<div class="cost-detail">
|
|
<span>Units (${model.material}):</span>
|
|
<span class="value">${model.units.toFixed(3)}</span>
|
|
</div>
|
|
<div class="cost-detail">
|
|
<span>Cost per Unit:</span>
|
|
<span class="value">$${model.costPerUnit.toFixed(2)}</span>
|
|
</div>
|
|
<div class="cost-detail total">
|
|
<span>Estimated Cost:</span>
|
|
<span>$${model.estimatedCost.toFixed(2)}</span>
|
|
</div>
|
|
<div style="font-size: 0.75rem; color: var(--text-secondary); margin-top: 0.5rem;">
|
|
Confidence: ${model.confidence}
|
|
</div>
|
|
</div>
|
|
`).join('');
|
|
|
|
costResults.innerHTML = cardsHtml + `
|
|
<div class="cost-summary" style="grid-column: 1 / -1;">
|
|
<h3>$${data.totalCost.toFixed(2)}</h3>
|
|
<p>Total Estimated Cost for ${data.models.length} model(s)</p>
|
|
<p style="font-size: 0.8rem; margin-top: 0.5rem;">Average: $${data.averageCost.toFixed(2)} per model</p>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
/**
|
|
* Calculate cost for single model
|
|
*/
|
|
function calculateModelCost(modelId, fileSize, materialType = 'pla') {
|
|
fetch(`${API_BASE}/models/${modelId}/cost?materialType=${materialType}`, {
|
|
headers: { 'Authorization': `Bearer ${authToken}` }
|
|
})
|
|
.then(res => res.json())
|
|
.then(data => {
|
|
return data;
|
|
})
|
|
.catch(err => console.error('Error calculating cost:', err));
|
|
}
|
|
|
|
/**
|
|
* Add cost badge to model card
|
|
*/
|
|
function addCostBadge(modelCard, cost) {
|
|
const existing = modelCard.querySelector('.model-cost-badge');
|
|
if (existing) existing.remove();
|
|
|
|
const badge = document.createElement('div');
|
|
badge.className = 'model-cost-badge';
|
|
badge.textContent = `$${cost.toFixed(2)}`;
|
|
modelCard.querySelector('.model-info')?.appendChild(badge);
|
|
}
|
|
|
|
/**
|
|
* Enhanced search with full-text support
|
|
*/
|
|
function handleAdvancedSearch(query) {
|
|
const params = new URLSearchParams({
|
|
search: query,
|
|
license: document.getElementById('filterLicense')?.value || '',
|
|
fileType: document.getElementById('filterFileType')?.value || '',
|
|
hasSupports: document.getElementById('filterSupports')?.checked || false,
|
|
sortBy: document.getElementById('sortBy')?.value || 'created_at',
|
|
sortOrder: document.getElementById('sortOrder')?.value || 'DESC'
|
|
});
|
|
|
|
fetch(`${API_BASE}/models?${params}`, {
|
|
headers: { 'Authorization': `Bearer ${authToken}` }
|
|
})
|
|
.then(res => res.json())
|
|
.then(data => {
|
|
displayModels(data.models);
|
|
})
|
|
.catch(err => console.error('Error searching:', err));
|
|
}
|
|
|
|
/**
|
|
* Apply all filters including license
|
|
*/
|
|
function applyFilters() {
|
|
const searchTerm = document.getElementById('searchInput')?.value || '';
|
|
handleAdvancedSearch(searchTerm);
|
|
}
|
|
|
|
/**
|
|
* Display license in model card
|
|
*/
|
|
function displayModelLicense(modelCard, license) {
|
|
const licenseEl = document.createElement('p');
|
|
licenseEl.style.fontSize = '0.75rem';
|
|
licenseEl.style.color = 'var(--text-secondary)';
|
|
licenseEl.textContent = `📜 ${license}`;
|
|
modelCard.querySelector('.model-info')?.appendChild(licenseEl);
|
|
}
|
|
|
|
/**
|
|
* Theme toggle
|
|
*/
|
|
function toggleTheme() {
|
|
if (typeof setTheme === 'function') {
|
|
const current = localStorage.getItem('theme') || 'light';
|
|
const newTheme = current === 'light' ? 'dark' : 'light';
|
|
setTheme(newTheme);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Update model card with additional info (license, cost)
|
|
*/
|
|
function enhanceModelCard(modelCard, model) {
|
|
// Add license info
|
|
if (model.license && model.license !== 'Unknown') {
|
|
displayModelLicense(modelCard, model.license);
|
|
}
|
|
|
|
// Add cost estimate for selected material
|
|
const defaultMaterial = localStorage.getItem('defaultMaterial') || 'pla';
|
|
// Note: Cost will be added when user interacts or opens calculator
|
|
}
|
|
|
|
// Auto-load materials on page init
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
// Load materials for the cost calculator
|
|
fetch(`${API_BASE}/models/config/materials`)
|
|
.then(res => res.json())
|
|
.then(data => {
|
|
console.log('Available materials:', data.materials);
|
|
})
|
|
.catch(err => console.error('Error loading materials:', err));
|
|
});
|
|
|
|
export {
|
|
showPrinterSettings,
|
|
loadPrinters,
|
|
showCostCalculator,
|
|
updateCostEstimate,
|
|
handleAdvancedSearch,
|
|
applyFilters,
|
|
toggleTheme
|
|
};
|