161 lines
5.1 KiB
JavaScript
161 lines
5.1 KiB
JavaScript
/**
|
||
* Common utilities for the SciPaperLoader application
|
||
*/
|
||
|
||
/**
|
||
* Display a flash message to the user as an overlay
|
||
* @param {string} message - The message to display
|
||
* @param {string} type - The type of message (success, error, warning, info)
|
||
* @param {number} duration - Duration in milliseconds (default: 5000)
|
||
*/
|
||
function showFlashMessage(message, type = "success", duration = 5000) {
|
||
const flashMsg = document.createElement("div");
|
||
const normalizedType = type === "error" ? "danger" : type;
|
||
flashMsg.className = `flash-overlay flash-${normalizedType}`;
|
||
|
||
// Get the appropriate icon based on type
|
||
const getIcon = (messageType) => {
|
||
switch (messageType) {
|
||
case "success":
|
||
return '<svg class="flash-icon" role="img" aria-label="Success:"><use xlink:href="#check-circle-fill"/></svg>';
|
||
case "danger":
|
||
return '<svg class="flash-icon" role="img" aria-label="Error:"><use xlink:href="#x-circle-fill"/></svg>';
|
||
case "warning":
|
||
return '<svg class="flash-icon" role="img" aria-label="Warning:"><use xlink:href="#exclamation-triangle-fill"/></svg>';
|
||
case "info":
|
||
return '<svg class="flash-icon" role="img" aria-label="Info:"><use xlink:href="#info-fill"/></svg>';
|
||
default:
|
||
return '<svg class="flash-icon" role="img" aria-label="Info:"><use xlink:href="#info-fill"/></svg>';
|
||
}
|
||
};
|
||
|
||
flashMsg.innerHTML = `
|
||
<div class="flash-content">
|
||
${getIcon(normalizedType)}
|
||
<div class="flash-message">${message}</div>
|
||
<button type="button" class="flash-close" onclick="this.parentElement.parentElement.remove()">×</button>
|
||
</div>
|
||
`;
|
||
|
||
// Handle stacking of multiple messages
|
||
const existingMessages = document.querySelectorAll('.flash-overlay');
|
||
existingMessages.forEach((msg, index) => {
|
||
msg.style.setProperty('--flash-index', index + 1);
|
||
});
|
||
|
||
// Add to page
|
||
document.body.appendChild(flashMsg);
|
||
|
||
// Auto dismiss
|
||
setTimeout(() => {
|
||
flashMsg.classList.add("fade-out");
|
||
setTimeout(() => {
|
||
if (flashMsg.parentNode) {
|
||
flashMsg.remove();
|
||
// Reposition remaining messages
|
||
const remainingMessages = document.querySelectorAll('.flash-overlay');
|
||
remainingMessages.forEach((msg, index) => {
|
||
msg.style.setProperty('--flash-index', index);
|
||
});
|
||
}
|
||
}, 300);
|
||
}, duration);
|
||
|
||
return flashMsg;
|
||
}
|
||
|
||
/**
|
||
* Create a status badge HTML element
|
||
* @param {string} status - The status to create a badge for
|
||
* @returns {string} HTML string for the status badge
|
||
*/
|
||
function createStatusBadge(status) {
|
||
switch (status) {
|
||
case "New":
|
||
return '<span class="badge bg-info">New</span>';
|
||
case "Pending":
|
||
return '<span class="badge bg-warning text-dark">Pending</span>';
|
||
case "Done":
|
||
return '<span class="badge bg-success">Done</span>';
|
||
case "Failed":
|
||
return '<span class="badge bg-danger">Failed</span>';
|
||
case "success":
|
||
return '<span class="badge bg-success">Success</span>';
|
||
case "error":
|
||
return '<span class="badge bg-danger">Error</span>';
|
||
case "pending":
|
||
return '<span class="badge bg-warning text-dark">Pending</span>';
|
||
default:
|
||
return `<span class="badge bg-secondary">${status}</span>`;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Format a timestamp to a readable time string
|
||
* @param {string} timestamp - ISO timestamp string
|
||
* @returns {string} Formatted time string
|
||
*/
|
||
function formatTimestamp(timestamp) {
|
||
const date = new Date(timestamp);
|
||
return date.toLocaleTimeString("de-DE", {
|
||
year: "2-digit",
|
||
month: "numeric",
|
||
day: "numeric",
|
||
hour: "2-digit",
|
||
minute: "2-digit",
|
||
second: "2-digit",
|
||
});
|
||
}
|
||
|
||
/**
|
||
* Truncate text to a specified length
|
||
* @param {string} text - The text to truncate
|
||
* @param {number} maxLength - Maximum length before truncation
|
||
* @returns {string} Truncated text with ellipsis if needed
|
||
*/
|
||
function truncateText(text, maxLength) {
|
||
return text.length > maxLength ? text.substring(0, maxLength) + "..." : text;
|
||
}
|
||
|
||
/**
|
||
* Toggle button loading state
|
||
* @param {HTMLElement} button - The button element
|
||
* @param {boolean} loading - Whether to show loading state
|
||
* @param {string} loadingText - Text to show when loading
|
||
*/
|
||
function toggleButtonLoading(button, loading, loadingText = "Loading...") {
|
||
if (loading) {
|
||
button.disabled = true;
|
||
button.dataset.originalText = button.innerHTML;
|
||
button.innerHTML = `<i class="fas fa-spinner fa-spin"></i> ${loadingText}`;
|
||
} else {
|
||
button.disabled = false;
|
||
button.innerHTML = button.dataset.originalText || button.innerHTML;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Generic fetch wrapper with error handling
|
||
* @param {string} url - The URL to fetch
|
||
* @param {object} options - Fetch options
|
||
* @returns {Promise} Fetch promise
|
||
*/
|
||
async function apiRequest(url, options = {}) {
|
||
const defaultOptions = {
|
||
headers: {
|
||
"Content-Type": "application/json",
|
||
},
|
||
};
|
||
|
||
const mergedOptions = { ...defaultOptions, ...options };
|
||
|
||
try {
|
||
const response = await fetch(url, mergedOptions);
|
||
const data = await response.json();
|
||
return data;
|
||
} catch (error) {
|
||
console.error(`API request failed for ${url}:`, error);
|
||
throw error;
|
||
}
|
||
}
|