805 lines
27 KiB
JavaScript
805 lines
27 KiB
JavaScript
// Additional Features for MakerStash
|
|
// This file contains all the new feature implementations
|
|
|
|
// State for bulk selection
|
|
let selectedModels = new Set();
|
|
let cachedModels = []; // Store models for later reference
|
|
|
|
// Filter and Sort Functions
|
|
function applyFilters() {
|
|
const fileType = document.getElementById('filterFileType').value;
|
|
const hasSupports = document.getElementById('filterSupports').checked;
|
|
const sortBy = document.getElementById('sortBy').value;
|
|
const sortOrder = document.getElementById('sortOrder').value;
|
|
const searchTerm = document.getElementById('searchInput').value;
|
|
|
|
loadAllModels(searchTerm, '', '', fileType, hasSupports, sortBy, sortOrder);
|
|
}
|
|
|
|
function clearFilters() {
|
|
document.getElementById('filterFileType').value = '';
|
|
document.getElementById('filterSupports').checked = false;
|
|
document.getElementById('sortBy').value = 'created_at';
|
|
document.getElementById('sortOrder').value = 'DESC';
|
|
document.getElementById('searchInput').value = '';
|
|
loadAllModels();
|
|
}
|
|
|
|
// Update loadAllModels to accept filter parameters
|
|
const originalLoadAllModels = window.loadAllModels;
|
|
window.loadAllModels = async function(search = '', tag = '', collection = '', fileType = '', hasSupports = false, sortBy = 'created_at', sortOrder = 'DESC') {
|
|
const grid = document.getElementById('modelsGrid');
|
|
grid.innerHTML = '<div class="loading-spinner"><i class="fas fa-spinner fa-spin"></i><p>Loading models...</p></div>';
|
|
|
|
try {
|
|
let url = `${API_BASE}/models?`;
|
|
if (search) url += `search=${encodeURIComponent(search)}&`;
|
|
if (tag) url += `tag=${encodeURIComponent(tag)}&`;
|
|
if (collection) url += `collection=${encodeURIComponent(collection)}&`;
|
|
if (fileType) url += `fileType=${encodeURIComponent(fileType)}&`;
|
|
if (hasSupports) url += `hasSupports=true&`;
|
|
url += `sortBy=${sortBy}&sortOrder=${sortOrder}`;
|
|
|
|
const response = await fetch(url, {
|
|
headers: authToken ? { 'Authorization': `Bearer ${authToken}` } : {}
|
|
});
|
|
const data = await response.json();
|
|
|
|
if (!response.ok) {
|
|
if (response.status === 401) {
|
|
grid.innerHTML = '<div class="loading-spinner"><p>Please log in to view models</p></div>';
|
|
return;
|
|
}
|
|
throw new Error(data.error || 'Failed to load models');
|
|
}
|
|
|
|
if (data.models && data.models.length > 0) {
|
|
displayModelsWithSelection(data.models);
|
|
} else {
|
|
grid.innerHTML = '<div class="loading-spinner"><p>No models found</p></div>';
|
|
}
|
|
} catch (error) {
|
|
grid.innerHTML = `<div class="loading-spinner"><p>Error loading models: ${error.message}</p></div>`;
|
|
console.error('Error loading models:', error);
|
|
}
|
|
};
|
|
|
|
// Display models with selection checkboxes
|
|
function displayModelsWithSelection(models) {
|
|
cachedModels = models; // Cache the models
|
|
const grid = document.getElementById('modelsGrid');
|
|
grid.innerHTML = '';
|
|
|
|
models.forEach(model => {
|
|
const card = document.createElement('div');
|
|
card.className = 'model-card';
|
|
if (selectedModels.has(model.id)) {
|
|
card.classList.add('selected');
|
|
}
|
|
|
|
card.onclick = (e) => {
|
|
if (!e.target.classList.contains('model-checkbox')) {
|
|
showModelDetails(model.id);
|
|
}
|
|
};
|
|
|
|
const fileIcon = getFileIcon(model.file_type);
|
|
const fileSize = formatFileSize(model.file_size);
|
|
const createdDate = new Date(model.created_at).toLocaleDateString();
|
|
|
|
const previewContent = model.preview_image
|
|
? `<img src="/${model.preview_image}" alt="${escapeHtml(model.name)}" />`
|
|
: `<i class="fas ${fileIcon}"></i>`;
|
|
|
|
const has3DViewer = ['.stl', '.obj', '.3mf'].includes(model.file_type);
|
|
|
|
card.innerHTML = `
|
|
<input type="checkbox" class="model-checkbox" ${selectedModels.has(model.id) ? 'checked' : ''}
|
|
onchange="toggleModelSelection(${model.id})" onclick="event.stopPropagation()">
|
|
<div class="model-preview ${model.preview_image ? 'has-thumbnail' : ''}">
|
|
${previewContent}
|
|
${has3DViewer ? `
|
|
<button class="btn-3d-preview" onclick="event.stopPropagation(); open3DViewer(${model.id})" title="View in 3D">
|
|
<i class="fas fa-cube"></i>
|
|
</button>
|
|
` : ''}
|
|
</div>
|
|
<div class="model-info">
|
|
<h3>${escapeHtml(model.name)}</h3>
|
|
<p>${escapeHtml(model.description || 'No description')}</p>
|
|
<div class="model-meta">
|
|
<span><i class="fas fa-file"></i> ${fileSize}</span>
|
|
<span><i class="fas fa-calendar"></i> ${createdDate}</span>
|
|
</div>
|
|
${model.tags && model.tags.length > 0 ? `
|
|
<div class="model-tags">
|
|
${model.tags.map(tag => `<span class="tag">${escapeHtml(tag)}</span>`).join('')}
|
|
</div>
|
|
` : ''}
|
|
</div>
|
|
`;
|
|
|
|
grid.appendChild(card);
|
|
});
|
|
}
|
|
|
|
// Bulk Selection Functions
|
|
function toggleModelSelection(modelId) {
|
|
if (selectedModels.has(modelId)) {
|
|
selectedModels.delete(modelId);
|
|
} else {
|
|
selectedModels.add(modelId);
|
|
}
|
|
updateBulkToolbar();
|
|
updateSelectedCards();
|
|
}
|
|
|
|
function updateBulkToolbar() {
|
|
const toolbar = document.getElementById('bulkToolbar');
|
|
const count = document.getElementById('bulkSelectionCount');
|
|
|
|
if (selectedModels.size > 0) {
|
|
toolbar.style.display = 'block';
|
|
count.textContent = `${selectedModels.size} selected`;
|
|
} else {
|
|
toolbar.style.display = 'none';
|
|
}
|
|
}
|
|
|
|
function updateSelectedCards() {
|
|
document.querySelectorAll('.model-card').forEach(card => {
|
|
const checkbox = card.querySelector('.model-checkbox');
|
|
if (checkbox && checkbox.checked) {
|
|
card.classList.add('selected');
|
|
} else {
|
|
card.classList.remove('selected');
|
|
}
|
|
});
|
|
}
|
|
|
|
function clearSelection() {
|
|
selectedModels.clear();
|
|
document.querySelectorAll('.model-checkbox').forEach(cb => cb.checked = false);
|
|
updateBulkToolbar();
|
|
updateSelectedCards();
|
|
}
|
|
|
|
// Bulk Operations
|
|
async function bulkAddTags() {
|
|
if (selectedModels.size === 0) return;
|
|
|
|
// Populate and show modal
|
|
showModal('bulkTagsModal');
|
|
}
|
|
|
|
async function handleBulkAddTags(event) {
|
|
event.preventDefault();
|
|
|
|
const tagsInput = document.getElementById('bulkTagsInput').value;
|
|
const tags = tagsInput.split(',').map(t => t.trim()).filter(t => t);
|
|
|
|
if (tags.length === 0) {
|
|
showNotification('Please enter at least one tag', 'error');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const response = await fetch(`${API_BASE}/bulk/tag`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Authorization': `Bearer ${authToken}`
|
|
},
|
|
body: JSON.stringify({
|
|
modelIds: Array.from(selectedModels),
|
|
tags: tags
|
|
})
|
|
});
|
|
|
|
if (response.ok) {
|
|
closeModal('bulkTagsModal');
|
|
showNotification('Tags added successfully!', 'success');
|
|
document.getElementById('bulkTagsInput').value = '';
|
|
clearSelection();
|
|
loadAllModels();
|
|
loadTags();
|
|
} else {
|
|
const data = await response.json();
|
|
showNotification(data.error || 'Failed to add tags', 'error');
|
|
}
|
|
} catch (error) {
|
|
showNotification('Failed to add tags: ' + error.message, 'error');
|
|
}
|
|
}
|
|
|
|
async function bulkMoveToCollection() {
|
|
if (selectedModels.size === 0) return;
|
|
|
|
// Populate collection dropdown
|
|
const select = document.getElementById('bulkMoveCollection');
|
|
select.innerHTML = '<option value="">None (Remove from collection)</option>' +
|
|
allCollections.map(c => `<option value="${c.id}">${escapeHtml(c.name)}</option>`).join('');
|
|
|
|
showModal('bulkMoveModal');
|
|
}
|
|
|
|
async function handleBulkMove(event) {
|
|
event.preventDefault();
|
|
|
|
const collectionId = document.getElementById('bulkMoveCollection').value || null;
|
|
|
|
try {
|
|
const response = await fetch(`${API_BASE}/bulk/move`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Authorization': `Bearer ${authToken}`
|
|
},
|
|
body: JSON.stringify({
|
|
modelIds: Array.from(selectedModels),
|
|
collectionId: collectionId
|
|
})
|
|
});
|
|
|
|
if (response.ok) {
|
|
closeModal('bulkMoveModal');
|
|
showNotification('Models moved successfully!', 'success');
|
|
clearSelection();
|
|
loadAllModels();
|
|
} else {
|
|
const data = await response.json();
|
|
showNotification(data.error || 'Failed to move models', 'error');
|
|
}
|
|
} catch (error) {
|
|
showNotification('Failed to move models: ' + error.message, 'error');
|
|
}
|
|
}
|
|
|
|
async function bulkDelete() {
|
|
if (selectedModels.size === 0) return;
|
|
|
|
if (!confirm(`Are you sure you want to delete ${selectedModels.size} models? This cannot be undone.`)) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const response = await fetch(`${API_BASE}/bulk/delete`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Authorization': `Bearer ${authToken}`
|
|
},
|
|
body: JSON.stringify({
|
|
modelIds: Array.from(selectedModels)
|
|
})
|
|
});
|
|
|
|
if (response.ok) {
|
|
showNotification('Models deleted successfully!', 'success');
|
|
clearSelection();
|
|
loadAllModels();
|
|
} else {
|
|
const data = await response.json();
|
|
showNotification(data.error || 'Failed to delete models', 'error');
|
|
}
|
|
} catch (error) {
|
|
showNotification('Failed to delete models: ' + error.message, 'error');
|
|
}
|
|
}
|
|
|
|
// Print Queue Functions
|
|
async function loadPrintQueue() {
|
|
try {
|
|
const response = await fetch(`${API_BASE}/print-queue`, {
|
|
headers: { 'Authorization': `Bearer ${authToken}` }
|
|
});
|
|
const data = await response.json();
|
|
|
|
const countBadge = document.getElementById('queueCount');
|
|
if (countBadge) {
|
|
countBadge.textContent = data.queue.length;
|
|
}
|
|
|
|
return data.queue;
|
|
} catch (error) {
|
|
console.error('Error loading print queue:', error);
|
|
return [];
|
|
}
|
|
}
|
|
|
|
async function showPrintQueueModal() {
|
|
showModal('printQueueModal');
|
|
|
|
const queueList = document.getElementById('printQueueList');
|
|
queueList.innerHTML = '<div class="loading-spinner">Loading queue...</div>';
|
|
|
|
const queue = await loadPrintQueue();
|
|
|
|
if (queue.length === 0) {
|
|
queueList.innerHTML = '<p style="text-align: center; color: var(--text-secondary);">Queue is empty</p>';
|
|
return;
|
|
}
|
|
|
|
queueList.innerHTML = queue.map(item => `
|
|
<div class="queue-item" data-id="${item.id}">
|
|
<div class="queue-item-info">
|
|
<h4>${escapeHtml(item.model_name)}</h4>
|
|
|
|
<div class="queue-details-grid">
|
|
<div class="queue-detail">
|
|
<label>Quantity:</label>
|
|
<span>${item.quantity || 1}</span>
|
|
</div>
|
|
<div class="queue-detail">
|
|
<label>Filament:</label>
|
|
<span>${item.filament_type ? `${item.filament_type.toUpperCase()} (${item.color})` : 'Not specified'}</span>
|
|
</div>
|
|
<div class="queue-detail">
|
|
<label>Temperatures:</label>
|
|
<span>${item.print_temp ? item.print_temp + '°C' : '—'} / ${item.bed_temp ? item.bed_temp + '°C' : '—'}</span>
|
|
</div>
|
|
<div class="queue-detail">
|
|
<label>Speed:</label>
|
|
<span>${item.print_speed ? item.print_speed.charAt(0).toUpperCase() + item.print_speed.slice(1) : 'Normal'}</span>
|
|
</div>
|
|
<div class="queue-detail">
|
|
<label>Support:</label>
|
|
<span>${item.support_structure ? item.support_structure.charAt(0).toUpperCase() + item.support_structure.slice(1) : 'None'}</span>
|
|
</div>
|
|
<div class="queue-detail">
|
|
<label>Infill:</label>
|
|
<span>${item.infill_density || 20}%</span>
|
|
</div>
|
|
<div class="queue-detail">
|
|
<label>Layer Height:</label>
|
|
<span>${item.layer_height || 0.2}mm</span>
|
|
</div>
|
|
<div class="queue-detail">
|
|
<label>Priority:</label>
|
|
<span>${getPriorityLabel(item.priority)}</span>
|
|
</div>
|
|
</div>
|
|
|
|
${item.special_instructions ? `
|
|
<div class="queue-notes">
|
|
<strong>Notes:</strong> ${escapeHtml(item.special_instructions)}
|
|
</div>
|
|
` : ''}
|
|
|
|
<small style="color: var(--text-secondary);">Added: ${new Date(item.added_at).toLocaleDateString()}</small>
|
|
</div>
|
|
|
|
<div class="queue-item-actions">
|
|
<button class="btn btn-sm btn-secondary" onclick="editPrintJob(${item.id})">
|
|
<i class="fas fa-edit"></i> Edit
|
|
</button>
|
|
<button class="btn btn-sm btn-success" onclick="completeQueueItem(${item.id})">
|
|
<i class="fas fa-check"></i> Done
|
|
</button>
|
|
<button class="btn btn-sm btn-danger" onclick="removeFromQueue(${item.id})">
|
|
<i class="fas fa-trash"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
`).join('');
|
|
}
|
|
|
|
function getPriorityLabel(priority) {
|
|
switch(parseInt(priority)) {
|
|
case 20: return 'Urgent';
|
|
case 10: return 'High';
|
|
case 5: return 'Medium';
|
|
default: return 'Low';
|
|
}
|
|
}
|
|
|
|
let currentEditPrintJobId = null;
|
|
|
|
async function editPrintJob(jobId) {
|
|
try {
|
|
// Get the queue item data
|
|
const response = await fetch(`${API_BASE}/print-queue`, {
|
|
headers: { 'Authorization': `Bearer ${authToken}` }
|
|
});
|
|
const data = await response.json();
|
|
const job = data.queue.find(q => q.id === jobId);
|
|
|
|
if (!job) {
|
|
showNotification('Job not found', 'error');
|
|
return;
|
|
}
|
|
|
|
currentEditPrintJobId = jobId;
|
|
|
|
// Populate the form
|
|
document.getElementById('editPrintModelName').value = job.model_name;
|
|
document.getElementById('editPrintQuantity').value = job.quantity || 1;
|
|
document.getElementById('editPrintFilamentType').value = job.filament_type || '';
|
|
document.getElementById('editPrintColor').value = job.color || '';
|
|
document.getElementById('editPrintTemp').value = job.print_temp || '';
|
|
document.getElementById('editPrintBedTemp').value = job.bed_temp || '';
|
|
document.getElementById('editPrintSpeed').value = job.print_speed || 'normal';
|
|
document.getElementById('editPrintSupport').value = job.support_structure || 'none';
|
|
document.getElementById('editPrintInfill').value = job.infill_density || 20;
|
|
document.getElementById('editPrintLayerHeight').value = job.layer_height || 0.2;
|
|
document.getElementById('editPrintNotes').value = job.special_instructions || '';
|
|
document.getElementById('editPrintPriority').value = job.priority || 0;
|
|
|
|
showModal('editPrintJobModal');
|
|
} catch (error) {
|
|
showNotification('Failed to load job details: ' + error.message, 'error');
|
|
console.error('Error:', error);
|
|
}
|
|
}
|
|
|
|
async function saveEditPrintJob(event) {
|
|
event.preventDefault();
|
|
|
|
if (!currentEditPrintJobId) {
|
|
showNotification('Job ID not set', 'error');
|
|
return;
|
|
}
|
|
|
|
const printData = {
|
|
quantity: parseInt(document.getElementById('editPrintQuantity').value),
|
|
filament_type: document.getElementById('editPrintFilamentType').value,
|
|
color: document.getElementById('editPrintColor').value,
|
|
print_temp: document.getElementById('editPrintTemp').value ? parseInt(document.getElementById('editPrintTemp').value) : null,
|
|
bed_temp: document.getElementById('editPrintBedTemp').value ? parseInt(document.getElementById('editPrintBedTemp').value) : null,
|
|
print_speed: document.getElementById('editPrintSpeed').value,
|
|
support_structure: document.getElementById('editPrintSupport').value,
|
|
infill_density: parseInt(document.getElementById('editPrintInfill').value),
|
|
layer_height: parseFloat(document.getElementById('editPrintLayerHeight').value),
|
|
special_instructions: document.getElementById('editPrintNotes').value,
|
|
priority: parseInt(document.getElementById('editPrintPriority').value)
|
|
};
|
|
|
|
try {
|
|
const response = await fetch(`${API_BASE}/print-queue/${currentEditPrintJobId}`, {
|
|
method: 'PUT',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Authorization': `Bearer ${authToken}`
|
|
},
|
|
body: JSON.stringify(printData)
|
|
});
|
|
|
|
const data = await response.json();
|
|
|
|
if (!response.ok) {
|
|
showNotification('Error saving changes: ' + (data.error || 'Unknown error'), 'error');
|
|
return;
|
|
}
|
|
|
|
showNotification('Print job updated successfully!', 'success');
|
|
closeModal('editPrintJobModal');
|
|
showPrintQueueModal(); // Refresh the queue display
|
|
} catch (error) {
|
|
showNotification('Failed to save changes: ' + error.message, 'error');
|
|
console.error('Error:', error);
|
|
}
|
|
}
|
|
|
|
async function removePrintJob() {
|
|
if (!currentEditPrintJobId) return;
|
|
|
|
if (!confirm('Are you sure you want to delete this print job?')) return;
|
|
|
|
try {
|
|
const response = await fetch(`${API_BASE}/print-queue/${currentEditPrintJobId}`, {
|
|
method: 'DELETE',
|
|
headers: { 'Authorization': `Bearer ${authToken}` }
|
|
});
|
|
|
|
if (!response.ok) {
|
|
showNotification('Error deleting job', 'error');
|
|
return;
|
|
}
|
|
|
|
showNotification('Print job deleted', 'success');
|
|
closeModal('editPrintJobModal');
|
|
showPrintQueueModal(); // Refresh the queue display
|
|
} catch (error) {
|
|
showNotification('Failed to delete job: ' + error.message, 'error');
|
|
}
|
|
}
|
|
|
|
async function bulkAddToQueue() {
|
|
if (selectedModels.size === 0) return;
|
|
|
|
// If only one model, show detailed form
|
|
if (selectedModels.size === 1) {
|
|
const modelId = Array.from(selectedModels)[0];
|
|
// Find the model in cached models
|
|
const model = cachedModels.find(m => m.id === modelId);
|
|
const modelName = model ? model.name : `Model ${modelId}`;
|
|
showPrintDetailsModal(modelId, modelName);
|
|
return;
|
|
}
|
|
|
|
// For multiple models, add them quickly with basic settings
|
|
try {
|
|
const promises = Array.from(selectedModels).map(modelId =>
|
|
fetch(`${API_BASE}/print-queue`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Authorization': `Bearer ${authToken}`
|
|
},
|
|
body: JSON.stringify({ modelId, priority: 0 })
|
|
})
|
|
);
|
|
|
|
await Promise.all(promises);
|
|
showNotification('Models added to print queue!', 'success');
|
|
clearSelection();
|
|
loadPrintQueue();
|
|
} catch (error) {
|
|
showNotification('Failed to add to queue: ' + error.message, 'error');
|
|
}
|
|
}
|
|
|
|
async function updateQueuePriority(queueId, priority) {
|
|
try {
|
|
await fetch(`${API_BASE}/print-queue/${queueId}`, {
|
|
method: 'PUT',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Authorization': `Bearer ${authToken}`
|
|
},
|
|
body: JSON.stringify({ priority: parseInt(priority) })
|
|
});
|
|
} catch (error) {
|
|
console.error('Error updating priority:', error);
|
|
}
|
|
}
|
|
|
|
async function completeQueueItem(queueId) {
|
|
try {
|
|
await fetch(`${API_BASE}/print-queue/${queueId}`, {
|
|
method: 'PUT',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Authorization': `Bearer ${authToken}`
|
|
},
|
|
body: JSON.stringify({ status: 'completed' })
|
|
});
|
|
showNotification('Marked as completed!', 'success');
|
|
showPrintQueueModal();
|
|
loadPrintQueue();
|
|
} catch (error) {
|
|
showNotification('Error: ' + error.message, 'error');
|
|
}
|
|
}
|
|
|
|
async function removeFromQueue(queueId) {
|
|
try {
|
|
await fetch(`${API_BASE}/print-queue/${queueId}`, {
|
|
method: 'DELETE',
|
|
headers: { 'Authorization': `Bearer ${authToken}` }
|
|
});
|
|
showNotification('Removed from queue', 'success');
|
|
showPrintQueueModal();
|
|
loadPrintQueue();
|
|
} catch (error) {
|
|
showNotification('Error: ' + error.message, 'error');
|
|
}
|
|
}
|
|
|
|
// Export/Import Functions
|
|
async function exportAllModels() {
|
|
try {
|
|
const response = await fetch(`${API_BASE}/export/all`, {
|
|
headers: { 'Authorization': `Bearer ${authToken}` }
|
|
});
|
|
|
|
if (response.ok) {
|
|
const blob = await response.blob();
|
|
const url = window.URL.createObjectURL(blob);
|
|
const a = document.createElement('a');
|
|
a.href = url;
|
|
a.download = `makerstash-export-${Date.now()}.zip`;
|
|
document.body.appendChild(a);
|
|
a.click();
|
|
window.URL.revokeObjectURL(url);
|
|
document.body.removeChild(a);
|
|
showNotification('Export started! Check your downloads.', 'success');
|
|
} else {
|
|
showNotification('Export failed', 'error');
|
|
}
|
|
} catch (error) {
|
|
showNotification('Export error: ' + error.message, 'error');
|
|
}
|
|
}
|
|
|
|
async function bulkExport() {
|
|
if (selectedModels.size === 0) return;
|
|
|
|
try {
|
|
const response = await fetch(`${API_BASE}/export/models`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Authorization': `Bearer ${authToken}`
|
|
},
|
|
body: JSON.stringify({ modelIds: Array.from(selectedModels) })
|
|
});
|
|
|
|
if (response.ok) {
|
|
const blob = await response.blob();
|
|
const url = window.URL.createObjectURL(blob);
|
|
const a = document.createElement('a');
|
|
a.href = url;
|
|
a.download = `makerstash-models-${Date.now()}.zip`;
|
|
document.body.appendChild(a);
|
|
a.click();
|
|
window.URL.revokeObjectURL(url);
|
|
document.body.removeChild(a);
|
|
showNotification('Export started!', 'success');
|
|
} else {
|
|
showNotification('Export failed', 'error');
|
|
}
|
|
} catch (error) {
|
|
showNotification('Export error: ' + error.message, 'error');
|
|
}
|
|
}
|
|
|
|
function showImportModal() {
|
|
// Populate collection dropdown
|
|
const select = document.getElementById('importCollection');
|
|
select.innerHTML = '<option value="">None</option>' +
|
|
allCollections.map(c => `<option value="${c.id}">${escapeHtml(c.name)}</option>`).join('');
|
|
|
|
showModal('importModal');
|
|
}
|
|
|
|
// Quick add function - creates a reference without file
|
|
async function handleQuickImport(event) {
|
|
event.preventDefault();
|
|
|
|
const url = document.getElementById('importUrl').value;
|
|
const name = document.getElementById('importName').value;
|
|
const description = document.getElementById('importDescription').value;
|
|
const creator = document.getElementById('importCreator').value;
|
|
const collectionId = document.getElementById('importCollection').value;
|
|
const tagsInput = document.getElementById('importTags').value;
|
|
const tags = tagsInput ? tagsInput.split(',').map(t => t.trim()).filter(t => t) : [];
|
|
|
|
// Create a placeholder file entry (we'll create a dummy text file)
|
|
const dummyContent = `This is a reference to a model from ${url}\n\nDownload the actual file from the URL above and upload it to replace this reference.`;
|
|
const blob = new Blob([dummyContent], { type: 'text/plain' });
|
|
const file = new File([blob], 'reference.txt', { type: 'text/plain' });
|
|
|
|
const formData = new FormData();
|
|
formData.append('files', file);
|
|
formData.append('name', name);
|
|
formData.append('description', description || '');
|
|
formData.append('creator', creator || '');
|
|
formData.append('source_url', url);
|
|
formData.append('collection_id', collectionId || '');
|
|
formData.append('is_supported', false);
|
|
formData.append('notes', `Reference from ${url}`);
|
|
if (tags.length > 0) {
|
|
formData.append('tags', JSON.stringify(tags));
|
|
}
|
|
|
|
try {
|
|
const response = await fetch(`${API_BASE}/models`, {
|
|
method: 'POST',
|
|
headers: { 'Authorization': `Bearer ${authToken}` },
|
|
body: formData
|
|
});
|
|
|
|
if (response.ok) {
|
|
closeModal('importModal');
|
|
showNotification('Reference saved! You can now download the file from the URL and upload it.', 'success');
|
|
// Clear form
|
|
document.getElementById('importUrl').value = '';
|
|
document.getElementById('importName').value = '';
|
|
document.getElementById('importDescription').value = '';
|
|
document.getElementById('importCreator').value = '';
|
|
document.getElementById('importTags').value = '';
|
|
loadAllModels();
|
|
} else {
|
|
const data = await response.json();
|
|
showNotification(data.error || 'Failed to save reference', 'error');
|
|
}
|
|
} catch (error) {
|
|
showNotification('Error: ' + error.message, 'error');
|
|
}
|
|
}
|
|
|
|
// Update auth function to show new sections
|
|
const originalUpdateUIForAuth = window.updateUIForAuth;
|
|
window.updateUIForAuth = function() {
|
|
if (originalUpdateUIForAuth) {
|
|
originalUpdateUIForAuth();
|
|
}
|
|
|
|
document.getElementById('loginBtn').style.display = 'none';
|
|
document.getElementById('registerBtn').style.display = 'none';
|
|
document.getElementById('userMenu').style.display = 'flex';
|
|
document.getElementById('username').textContent = currentUser.username;
|
|
document.getElementById('uploadBtn').style.display = 'block';
|
|
document.getElementById('createCollectionBtn').style.display = 'block';
|
|
document.getElementById('printQueueSection').style.display = 'block';
|
|
document.getElementById('exportSection').style.display = 'block';
|
|
|
|
// Load print queue
|
|
if (authToken) {
|
|
loadPrintQueue();
|
|
}
|
|
};
|
|
|
|
// Print Details Modal Functions
|
|
let currentPrintModelId = null;
|
|
|
|
function showPrintDetailsModal(modelId, modelName) {
|
|
currentPrintModelId = modelId;
|
|
document.getElementById('printModelName').value = modelName;
|
|
document.getElementById('printQuantity').value = 1;
|
|
document.getElementById('printFilamentType').value = '';
|
|
document.getElementById('printColor').value = '';
|
|
document.getElementById('printTemp').value = '';
|
|
document.getElementById('printBedTemp').value = '';
|
|
document.getElementById('printSpeed').value = 'normal';
|
|
document.getElementById('printSupport').value = 'none';
|
|
document.getElementById('printInfill').value = 20;
|
|
document.getElementById('printLayerHeight').value = 0.2;
|
|
document.getElementById('printNotes').value = '';
|
|
document.getElementById('printPriority').value = 0;
|
|
|
|
showModal('printDetailsModal');
|
|
}
|
|
|
|
async function savePrintDetails(event) {
|
|
event.preventDefault();
|
|
|
|
if (!currentPrintModelId) {
|
|
showNotification('Model ID not set', 'error');
|
|
return;
|
|
}
|
|
|
|
const printData = {
|
|
modelId: currentPrintModelId,
|
|
quantity: parseInt(document.getElementById('printQuantity').value),
|
|
filament_type: document.getElementById('printFilamentType').value,
|
|
color: document.getElementById('printColor').value,
|
|
print_temp: document.getElementById('printTemp').value ? parseInt(document.getElementById('printTemp').value) : null,
|
|
bed_temp: document.getElementById('printBedTemp').value ? parseInt(document.getElementById('printBedTemp').value) : null,
|
|
print_speed: document.getElementById('printSpeed').value,
|
|
support_structure: document.getElementById('printSupport').value,
|
|
infill_density: parseInt(document.getElementById('printInfill').value),
|
|
layer_height: parseFloat(document.getElementById('printLayerHeight').value),
|
|
special_instructions: document.getElementById('printNotes').value,
|
|
priority: parseInt(document.getElementById('printPriority').value)
|
|
};
|
|
|
|
try {
|
|
const response = await fetch(`${API_BASE}/print-queue`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Authorization': `Bearer ${authToken}`
|
|
},
|
|
body: JSON.stringify(printData)
|
|
});
|
|
|
|
const data = await response.json();
|
|
|
|
if (!response.ok) {
|
|
showNotification('Error adding to queue: ' + (data.error || 'Unknown error'), 'error');
|
|
return;
|
|
}
|
|
|
|
showNotification('Model added to print queue with specifications!', 'success');
|
|
closeModal('printDetailsModal');
|
|
loadPrintQueue();
|
|
clearSelection();
|
|
} catch (error) {
|
|
showNotification('Failed to add to queue: ' + error.message, 'error');
|
|
console.error('Error:', error);
|
|
}
|
|
}
|
|
|
|
console.log('Additional features loaded');
|