758 lines
24 KiB
JavaScript
758 lines
24 KiB
JavaScript
// API Base URL
|
|
const API_BASE = '/api';
|
|
|
|
// State
|
|
let currentUser = null;
|
|
let authToken = null;
|
|
let allCollections = [];
|
|
let allTags = [];
|
|
|
|
// Initialize
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
checkAuth();
|
|
// loadAllModels, loadCollections, and loadTags will be called after auth check succeeds
|
|
});
|
|
|
|
// Auth Functions
|
|
function checkAuth() {
|
|
authToken = localStorage.getItem('authToken');
|
|
if (authToken) {
|
|
fetch(`${API_BASE}/auth/me`, {
|
|
headers: { 'Authorization': `Bearer ${authToken}` }
|
|
})
|
|
.then(res => res.json())
|
|
.then(data => {
|
|
if (data.user) {
|
|
currentUser = data.user;
|
|
// Apply user's theme preference
|
|
if (currentUser.theme && typeof setTheme === 'function') {
|
|
setTheme(currentUser.theme);
|
|
}
|
|
updateUIForAuth();
|
|
} else {
|
|
logout();
|
|
}
|
|
})
|
|
.catch(() => logout());
|
|
}
|
|
}
|
|
|
|
function updateUIForAuth() {
|
|
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';
|
|
|
|
// Load user's data after authentication
|
|
loadAllModels();
|
|
loadCollections();
|
|
loadTags();
|
|
}
|
|
|
|
async function handleLogin(event) {
|
|
event.preventDefault();
|
|
|
|
const username = document.getElementById('loginUsername').value;
|
|
const password = document.getElementById('loginPassword').value;
|
|
|
|
try {
|
|
const response = await fetch(`${API_BASE}/auth/login`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ username, password })
|
|
});
|
|
|
|
const data = await response.json();
|
|
|
|
if (response.ok) {
|
|
authToken = data.token;
|
|
currentUser = data.user;
|
|
localStorage.setItem('authToken', authToken);
|
|
closeModal('loginModal');
|
|
updateUIForAuth();
|
|
} else {
|
|
showNotification(data.error || 'Login failed', 'error');
|
|
}
|
|
} catch (error) {
|
|
showNotification('Login failed: ' + error.message, 'error');
|
|
}
|
|
}
|
|
|
|
async function handleRegister(event) {
|
|
event.preventDefault();
|
|
|
|
const username = document.getElementById('registerUsername').value;
|
|
const email = document.getElementById('registerEmail').value;
|
|
const password = document.getElementById('registerPassword').value;
|
|
|
|
try {
|
|
const response = await fetch(`${API_BASE}/auth/register`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ username, email, password })
|
|
});
|
|
|
|
const data = await response.json();
|
|
|
|
if (response.ok) {
|
|
authToken = data.token;
|
|
currentUser = data.user;
|
|
localStorage.setItem('authToken', authToken);
|
|
closeModal('registerModal');
|
|
updateUIForAuth();
|
|
showNotification('Registration successful!', 'success');
|
|
} else {
|
|
showNotification(data.error || 'Registration failed', 'error');
|
|
}
|
|
} catch (error) {
|
|
showNotification('Registration failed: ' + error.message, 'error');
|
|
}
|
|
}
|
|
|
|
function logout() {
|
|
authToken = null;
|
|
currentUser = null;
|
|
localStorage.removeItem('authToken');
|
|
document.getElementById('loginBtn').style.display = 'block';
|
|
document.getElementById('registerBtn').style.display = 'block';
|
|
document.getElementById('userMenu').style.display = 'none';
|
|
document.getElementById('uploadBtn').style.display = 'none';
|
|
document.getElementById('createCollectionBtn').style.display = 'none';
|
|
|
|
// Clear models display
|
|
const grid = document.getElementById('modelsGrid');
|
|
grid.innerHTML = '<div class="loading-spinner"><p>Please log in to view models</p></div>';
|
|
|
|
// Clear sidebar sections
|
|
document.getElementById('printQueueSection').style.display = 'none';
|
|
document.getElementById('exportSection').style.display = 'none';
|
|
}
|
|
|
|
// Models Functions
|
|
async function loadAllModels(search = '', tag = '', collection = '') {
|
|
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)}&`;
|
|
|
|
const response = await fetch(url);
|
|
const data = await response.json();
|
|
|
|
if (data.models && data.models.length > 0) {
|
|
displayModels(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</p></div>';
|
|
console.error('Error loading models:', error);
|
|
}
|
|
}
|
|
|
|
function displayModels(models) {
|
|
const grid = document.getElementById('modelsGrid');
|
|
grid.innerHTML = '';
|
|
|
|
models.forEach(model => {
|
|
const card = document.createElement('div');
|
|
card.className = 'model-card';
|
|
card.onclick = () => showModelDetails(model.id);
|
|
|
|
const fileIcon = getFileIcon(model.file_type);
|
|
const fileSize = formatFileSize(model.file_size);
|
|
const createdDate = new Date(model.created_at).toLocaleDateString();
|
|
|
|
// Check if thumbnail exists
|
|
const previewContent = model.preview_image
|
|
? `<img src="/${model.preview_image}" alt="${escapeHtml(model.name)}" />`
|
|
: `<i class="fas ${fileIcon}"></i>`;
|
|
|
|
// Check if 3D viewer is available
|
|
const has3DViewer = ['.stl', '.obj', '.3mf'].includes(model.file_type);
|
|
|
|
card.innerHTML = `
|
|
<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);
|
|
});
|
|
}
|
|
|
|
async function showModelDetails(modelId) {
|
|
try {
|
|
const response = await fetch(`${API_BASE}/models/${modelId}`);
|
|
const model = await response.json();
|
|
|
|
const detailsHtml = `
|
|
<div class="model-details-header">
|
|
<div>
|
|
<h2>${escapeHtml(model.name)}</h2>
|
|
${model.creator ? `<p>By ${escapeHtml(model.creator)}</p>` : ''}
|
|
</div>
|
|
<div class="model-details-actions">
|
|
${['.stl', '.obj', '.3mf'].includes(model.file_type) ? `
|
|
<button class="btn btn-primary" onclick="closeModal('modelModal'); open3DViewer(${model.id})">
|
|
<i class="fas fa-cube"></i> View in 3D
|
|
</button>
|
|
` : ''}
|
|
<a href="/api/models/${model.id}/download" class="btn btn-primary" download>
|
|
<i class="fas fa-download"></i> ${model.files && model.files.length > 0 ? 'Download All (ZIP)' : 'Download'}
|
|
</a>
|
|
${currentUser && currentUser.id === model.user_id ? `
|
|
<button class="btn btn-primary" onclick="showEditModal(${model.id})">
|
|
<i class="fas fa-edit"></i> Edit
|
|
</button>
|
|
<button class="btn btn-danger" onclick="deleteModel(${model.id})">
|
|
<i class="fas fa-trash"></i> Delete
|
|
</button>
|
|
` : ''}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="model-details-content">
|
|
<div>
|
|
<div class="details-section">
|
|
<h3>Description</h3>
|
|
<p>${escapeHtml(model.description || 'No description available')}</p>
|
|
</div>
|
|
|
|
${model.notes ? `
|
|
<div class="details-section">
|
|
<h3>Notes</h3>
|
|
<p>${escapeHtml(model.notes)}</p>
|
|
</div>
|
|
` : ''}
|
|
|
|
<div class="details-section">
|
|
<h3>Tags</h3>
|
|
<div class="tags-container">
|
|
${model.tags && model.tags.length > 0
|
|
? model.tags.map(tag => `<span class="tag">${escapeHtml(tag)}</span>`).join('')
|
|
: '<span class="loading">No tags</span>'}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<div class="details-section">
|
|
<h3>File Information</h3>
|
|
<p><span class="details-label">Primary File:</span> ${escapeHtml(model.file_name)}</p>
|
|
<p><span class="details-label">File Type:</span> ${escapeHtml(model.file_type)}</p>
|
|
<p><span class="details-label">File Size:</span> ${formatFileSize(model.file_size)}</p>
|
|
<p><span class="details-label">Uploaded:</span> ${new Date(model.created_at).toLocaleString()}</p>
|
|
${model.files && model.files.length > 0 ? `
|
|
<div style="margin-top: 1rem;">
|
|
<p><strong>Additional Files (${model.files.length}):</strong></p>
|
|
<ul style="margin-left: 1.5rem; margin-top: 0.5rem;">
|
|
${model.files.map(file => `
|
|
<li style="margin-bottom: 0.5rem;">
|
|
${escapeHtml(file.file_name)}
|
|
<span style="color: var(--text-secondary);">(${formatFileSize(file.file_size)})</span>
|
|
</li>
|
|
`).join('')}
|
|
</ul>
|
|
</div>
|
|
` : ''}
|
|
</div>
|
|
|
|
${model.collection_name ? `
|
|
<div class="details-section">
|
|
<h3>Collection</h3>
|
|
<p>${escapeHtml(model.collection_name)}</p>
|
|
</div>
|
|
` : ''}
|
|
|
|
${model.source_url ? `
|
|
<div class="details-section">
|
|
<h3>Source</h3>
|
|
<p><a href="${escapeHtml(model.source_url)}" target="_blank">${escapeHtml(model.source_url)}</a></p>
|
|
</div>
|
|
` : ''}
|
|
|
|
<div class="details-section">
|
|
<h3>Support Status</h3>
|
|
<p>${model.is_supported ? 'Includes supports' : 'No supports'}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
document.getElementById('modelDetails').innerHTML = detailsHtml;
|
|
showModal('modelModal');
|
|
} catch (error) {
|
|
showNotification('Error loading model details', 'error');
|
|
console.error('Error:', error);
|
|
}
|
|
}
|
|
|
|
async function handleUpload(event) {
|
|
event.preventDefault();
|
|
|
|
if (!authToken) {
|
|
showNotification('Please login to upload models', 'error');
|
|
return;
|
|
}
|
|
|
|
const formData = new FormData();
|
|
|
|
// Handle multiple files
|
|
const fileInput = document.getElementById('uploadFile');
|
|
const files = fileInput.files;
|
|
if (files.length === 0) {
|
|
showNotification('Please select at least one file', 'error');
|
|
return;
|
|
}
|
|
|
|
// Append all files
|
|
for (let i = 0; i < files.length; i++) {
|
|
formData.append('files', files[i]);
|
|
}
|
|
|
|
formData.append('name', document.getElementById('uploadName').value);
|
|
formData.append('description', document.getElementById('uploadDescription').value);
|
|
formData.append('creator', document.getElementById('uploadCreator').value);
|
|
formData.append('source_url', document.getElementById('uploadSourceUrl').value);
|
|
formData.append('collection_id', document.getElementById('uploadCollection').value);
|
|
formData.append('is_supported', document.getElementById('uploadSupported').checked);
|
|
formData.append('notes', document.getElementById('uploadNotes').value);
|
|
|
|
const tagsInput = document.getElementById('uploadTags').value;
|
|
if (tagsInput) {
|
|
const tags = tagsInput.split(',').map(t => t.trim()).filter(t => t);
|
|
formData.append('tags', JSON.stringify(tags));
|
|
}
|
|
|
|
try {
|
|
const response = await fetch(`${API_BASE}/models`, {
|
|
method: 'POST',
|
|
headers: { 'Authorization': `Bearer ${authToken}` },
|
|
body: formData
|
|
});
|
|
|
|
const data = await response.json();
|
|
|
|
if (response.ok) {
|
|
closeModal('uploadModal');
|
|
const message = data.fileCount > 1
|
|
? `Model uploaded with ${data.fileCount} files!`
|
|
: 'Model uploaded successfully!';
|
|
showNotification(message, 'success');
|
|
loadAllModels();
|
|
event.target.reset();
|
|
} else {
|
|
showNotification(data.error || 'Upload failed', 'error');
|
|
}
|
|
} catch (error) {
|
|
showNotification('Upload failed: ' + error.message, 'error');
|
|
}
|
|
}
|
|
|
|
async function deleteModel(modelId) {
|
|
if (!confirm('Are you sure you want to delete this model?')) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const response = await fetch(`${API_BASE}/models/${modelId}`, {
|
|
method: 'DELETE',
|
|
headers: { 'Authorization': `Bearer ${authToken}` }
|
|
});
|
|
|
|
if (response.ok) {
|
|
closeModal('modelModal');
|
|
showNotification('Model deleted successfully', 'success');
|
|
loadAllModels();
|
|
} else {
|
|
const data = await response.json();
|
|
showNotification(data.error || 'Delete failed', 'error');
|
|
}
|
|
} catch (error) {
|
|
showNotification('Delete failed: ' + error.message, 'error');
|
|
}
|
|
}
|
|
|
|
// Collections Functions
|
|
async function loadCollections() {
|
|
try {
|
|
const response = await fetch(`${API_BASE}/collections`);
|
|
const data = await response.json();
|
|
allCollections = data.collections;
|
|
|
|
const collectionsList = document.getElementById('collectionsList');
|
|
if (data.collections.length > 0) {
|
|
collectionsList.innerHTML = data.collections.map(collection => `
|
|
<li>
|
|
<a href="#" onclick="loadAllModels('', '', ${collection.id}); return false;">
|
|
<i class="fas fa-folder"></i> ${escapeHtml(collection.name)} (${collection.model_count})
|
|
</a>
|
|
</li>
|
|
`).join('');
|
|
} else {
|
|
collectionsList.innerHTML = '<li class="loading">No collections</li>';
|
|
}
|
|
|
|
// Update upload modal collection select
|
|
const uploadCollectionSelect = document.getElementById('uploadCollection');
|
|
uploadCollectionSelect.innerHTML = '<option value="">None</option>' +
|
|
data.collections.map(c => `<option value="${c.id}">${escapeHtml(c.name)}</option>`).join('');
|
|
|
|
} catch (error) {
|
|
console.error('Error loading collections:', error);
|
|
}
|
|
}
|
|
|
|
async function handleCreateCollection(event) {
|
|
event.preventDefault();
|
|
|
|
if (!authToken) {
|
|
showNotification('Please login to create collections', 'error');
|
|
return;
|
|
}
|
|
|
|
const name = document.getElementById('collectionName').value;
|
|
const description = document.getElementById('collectionDescription').value;
|
|
|
|
try {
|
|
const response = await fetch(`${API_BASE}/collections`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Authorization': `Bearer ${authToken}`
|
|
},
|
|
body: JSON.stringify({ name, description })
|
|
});
|
|
|
|
const data = await response.json();
|
|
|
|
if (response.ok) {
|
|
closeModal('createCollectionModal');
|
|
showNotification('Collection created successfully!', 'success');
|
|
loadCollections();
|
|
event.target.reset();
|
|
} else {
|
|
showNotification(data.error || 'Failed to create collection', 'error');
|
|
}
|
|
} catch (error) {
|
|
showNotification('Failed to create collection: ' + error.message, 'error');
|
|
}
|
|
}
|
|
|
|
function showCollections() {
|
|
loadCollections();
|
|
showNotification('Showing all collections', 'success');
|
|
}
|
|
|
|
// Tags Functions
|
|
async function loadTags() {
|
|
try {
|
|
const response = await fetch(`${API_BASE}/tags`);
|
|
const data = await response.json();
|
|
allTags = data.tags;
|
|
|
|
const tagsList = document.getElementById('tagsList');
|
|
if (data.tags.length > 0) {
|
|
const topTags = data.tags.sort((a, b) => b.model_count - a.model_count).slice(0, 10);
|
|
tagsList.innerHTML = topTags.map(tag => `
|
|
<span class="tag" style="background-color: ${tag.color}" onclick="loadAllModels('', '${escapeHtml(tag.name)}', '')">
|
|
${escapeHtml(tag.name)} (${tag.model_count})
|
|
</span>
|
|
`).join('');
|
|
} else {
|
|
tagsList.innerHTML = '<span class="loading">No tags</span>';
|
|
}
|
|
} catch (error) {
|
|
console.error('Error loading tags:', error);
|
|
}
|
|
}
|
|
|
|
function showTags() {
|
|
loadTags();
|
|
showNotification('Showing all tags', 'success');
|
|
}
|
|
|
|
// Search Function
|
|
let searchTimeout;
|
|
function handleSearch() {
|
|
clearTimeout(searchTimeout);
|
|
searchTimeout = setTimeout(() => {
|
|
const searchTerm = document.getElementById('searchInput').value;
|
|
loadAllModels(searchTerm);
|
|
}, 300);
|
|
}
|
|
|
|
// Modal Functions
|
|
function showModal(modalId) {
|
|
document.getElementById(modalId).style.display = 'block';
|
|
}
|
|
|
|
function closeModal(modalId) {
|
|
document.getElementById(modalId).style.display = 'none';
|
|
}
|
|
|
|
function showLoginModal() {
|
|
showModal('loginModal');
|
|
}
|
|
|
|
function showRegisterModal() {
|
|
showModal('registerModal');
|
|
}
|
|
|
|
function showUploadModal() {
|
|
if (!authToken) {
|
|
showNotification('Please login to upload models', 'error');
|
|
showLoginModal();
|
|
return;
|
|
}
|
|
showModal('uploadModal');
|
|
}
|
|
|
|
function showCreateCollectionModal() {
|
|
if (!authToken) {
|
|
showNotification('Please login to create collections', 'error');
|
|
showLoginModal();
|
|
return;
|
|
}
|
|
showModal('createCollectionModal');
|
|
}
|
|
|
|
function showSettingsModal() {
|
|
if (!authToken) {
|
|
showNotification('Please login to access settings', 'error');
|
|
showLoginModal();
|
|
return;
|
|
}
|
|
// Populate current email
|
|
document.getElementById('currentEmailDisplay').value = currentUser.email;
|
|
// Reset forms
|
|
document.getElementById('emailTab').querySelector('form').reset();
|
|
document.getElementById('passwordTab').querySelector('form').reset();
|
|
// Show email tab by default
|
|
switchSettingsTab('email');
|
|
showModal('settingsModal');
|
|
}
|
|
|
|
function switchSettingsTab(tab) {
|
|
// Hide all tabs
|
|
document.querySelectorAll('.tab-content').forEach(el => el.classList.remove('active'));
|
|
document.querySelectorAll('.tab-button').forEach(el => el.classList.remove('active'));
|
|
|
|
// Show selected tab
|
|
document.getElementById(tab + 'Tab').classList.add('active');
|
|
event.target.classList.add('active');
|
|
}
|
|
|
|
function updateEmail(event) {
|
|
event.preventDefault();
|
|
const newEmail = document.getElementById('newEmail').value;
|
|
const password = document.getElementById('emailConfirmPassword').value;
|
|
|
|
fetch(`${API_BASE}/auth/me/email`, {
|
|
method: 'PUT',
|
|
headers: {
|
|
'Authorization': `Bearer ${authToken}`,
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify({ newEmail, password })
|
|
})
|
|
.then(res => res.json())
|
|
.then(data => {
|
|
if (data.error) {
|
|
showNotification(data.error, 'error');
|
|
} else {
|
|
showNotification('Email updated successfully', 'success');
|
|
currentUser.email = newEmail;
|
|
document.getElementById('currentEmailDisplay').value = newEmail;
|
|
event.target.reset();
|
|
}
|
|
})
|
|
.catch(err => showNotification('Error updating email', 'error'));
|
|
}
|
|
|
|
function updatePassword(event) {
|
|
event.preventDefault();
|
|
const currentPassword = document.getElementById('currentPassword').value;
|
|
const newPassword = document.getElementById('newPassword').value;
|
|
const confirmNewPassword = document.getElementById('confirmNewPassword').value;
|
|
|
|
if (newPassword !== confirmNewPassword) {
|
|
showNotification('Passwords do not match', 'error');
|
|
return;
|
|
}
|
|
|
|
fetch(`${API_BASE}/auth/me/password`, {
|
|
method: 'PUT',
|
|
headers: {
|
|
'Authorization': `Bearer ${authToken}`,
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify({ currentPassword, newPassword })
|
|
})
|
|
.then(res => res.json())
|
|
.then(data => {
|
|
if (data.error) {
|
|
showNotification(data.error, 'error');
|
|
} else {
|
|
showNotification('Password updated successfully', 'success');
|
|
event.target.reset();
|
|
}
|
|
})
|
|
.catch(err => showNotification('Error updating password', 'error'));
|
|
}
|
|
|
|
// Close modals when clicking outside
|
|
window.onclick = function(event) {
|
|
if (event.target.classList.contains('modal')) {
|
|
event.target.style.display = 'none';
|
|
}
|
|
}
|
|
|
|
// Utility Functions
|
|
function escapeHtml(text) {
|
|
const map = {
|
|
'&': '&',
|
|
'<': '<',
|
|
'>': '>',
|
|
'"': '"',
|
|
"'": '''
|
|
};
|
|
return text ? String(text).replace(/[&<>"']/g, m => map[m]) : '';
|
|
}
|
|
|
|
function formatFileSize(bytes) {
|
|
if (!bytes) return '0 Bytes';
|
|
const k = 1024;
|
|
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i];
|
|
}
|
|
|
|
function getFileIcon(fileType) {
|
|
const iconMap = {
|
|
'.stl': 'fa-cube',
|
|
'.obj': 'fa-cubes',
|
|
'.3mf': 'fa-box',
|
|
'.gcode': 'fa-code',
|
|
'.zip': 'fa-file-archive'
|
|
};
|
|
return iconMap[fileType] || 'fa-file';
|
|
}
|
|
|
|
function showNotification(message, type) {
|
|
// Simple console notification for now
|
|
console.log(`${type.toUpperCase()}: ${message}`);
|
|
|
|
// You can implement a toast notification system here
|
|
alert(message);
|
|
}
|
|
|
|
// Edit Model Functions
|
|
async function showEditModal(modelId) {
|
|
try {
|
|
// Fetch model details
|
|
const response = await fetch(`${API_BASE}/models/${modelId}`);
|
|
const model = await response.json();
|
|
|
|
// Populate form fields
|
|
document.getElementById('editModelId').value = model.id;
|
|
document.getElementById('editModelName').value = model.name || '';
|
|
document.getElementById('editModelDescription').value = model.description || '';
|
|
document.getElementById('editModelCreator').value = model.creator || '';
|
|
document.getElementById('editModelSourceUrl').value = model.source_url || '';
|
|
document.getElementById('editModelNotes').value = model.notes || '';
|
|
document.getElementById('editModelSupported').checked = model.is_supported === 1;
|
|
|
|
// Populate tags
|
|
if (model.tags && model.tags.length > 0) {
|
|
document.getElementById('editModelTags').value = model.tags.join(', ');
|
|
} else {
|
|
document.getElementById('editModelTags').value = '';
|
|
}
|
|
|
|
// Populate collection dropdown
|
|
const collectionSelect = document.getElementById('editModelCollection');
|
|
collectionSelect.innerHTML = '<option value="">None</option>' +
|
|
allCollections.map(c =>
|
|
`<option value="${c.id}" ${c.id === model.collection_id ? 'selected' : ''}>${escapeHtml(c.name)}</option>`
|
|
).join('');
|
|
|
|
// Close model details modal and show edit modal
|
|
closeModal('modelModal');
|
|
showModal('editModelModal');
|
|
} catch (error) {
|
|
console.error('Error loading model for edit:', error);
|
|
showNotification('Failed to load model details', 'error');
|
|
}
|
|
}
|
|
|
|
async function handleEditModel(event) {
|
|
event.preventDefault();
|
|
|
|
if (!authToken) {
|
|
showNotification('Please login to edit models', 'error');
|
|
return;
|
|
}
|
|
|
|
const modelId = document.getElementById('editModelId').value;
|
|
const tagsInput = document.getElementById('editModelTags').value;
|
|
const tags = tagsInput ? tagsInput.split(',').map(t => t.trim()).filter(t => t) : [];
|
|
|
|
const updateData = {
|
|
name: document.getElementById('editModelName').value,
|
|
description: document.getElementById('editModelDescription').value,
|
|
creator: document.getElementById('editModelCreator').value,
|
|
source_url: document.getElementById('editModelSourceUrl').value,
|
|
collection_id: document.getElementById('editModelCollection').value || null,
|
|
is_supported: document.getElementById('editModelSupported').checked,
|
|
notes: document.getElementById('editModelNotes').value,
|
|
tags: tags
|
|
};
|
|
|
|
try {
|
|
const response = await fetch(`${API_BASE}/models/${modelId}`, {
|
|
method: 'PUT',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Authorization': `Bearer ${authToken}`
|
|
},
|
|
body: JSON.stringify(updateData)
|
|
});
|
|
|
|
const data = await response.json();
|
|
|
|
if (response.ok) {
|
|
closeModal('editModelModal');
|
|
showNotification('Model updated successfully!', 'success');
|
|
loadAllModels(); // Refresh the models list
|
|
} else {
|
|
showNotification(data.error || 'Failed to update model', 'error');
|
|
}
|
|
} catch (error) {
|
|
showNotification('Failed to update model: ' + error.message, 'error');
|
|
}
|
|
}
|