176 lines
5.4 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 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="removeFlashMessage(this.parentElement.parentElement)">×</button>
</div>
`;
// Add to page first
document.body.appendChild(flashMsg);
// Position all messages in stack
updateFlashMessagePositions();
// Auto dismiss
setTimeout(() => {
removeFlashMessage(flashMsg);
}, duration);
return flashMsg;
}
/**
* Remove a flash message and update positions
* @param {HTMLElement} flashMsg - The flash message element to remove
*/
function removeFlashMessage(flashMsg) {
if (!flashMsg || !flashMsg.parentNode) return;
flashMsg.classList.add("fade-out");
setTimeout(() => {
if (flashMsg.parentNode) {
flashMsg.remove();
updateFlashMessagePositions();
}
}, 300);
}
/**
* Update positions of all flash messages to create a proper stack
*/
function updateFlashMessagePositions() {
const messages = document.querySelectorAll(".flash-overlay:not(.fade-out)");
messages.forEach((msg, index) => {
const topPosition = 20 + index * 90; // 90px spacing between messages
msg.style.top = `${topPosition}px`;
msg.style.zIndex = 9999 - index; // Higher z-index for newer messages
});
}
/**
* 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;
}
}