Add circular progress indicator with percentage for capture tests
Some checks failed
Validate / validate (push) Failing after 9s
Some checks failed
Validate / validate (push) Failing after 9s
- Replace simple spinner with SVG circular progress ring - Show progress percentage (0-100%) in center of ring - Animate progress based on capture duration - Update every 100ms for smooth animation - Clean up timer on completion or cancellation UI improvements: - Full-page overlay with blur backdrop - Large percentage display (28px) - Progress ring uses primary color - Clean, minimal design Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -2328,6 +2328,115 @@ function closeTemplateModal() {
|
||||
currentEditingTemplateId = null;
|
||||
}
|
||||
|
||||
// Show full-page overlay spinner with progress
|
||||
function showOverlaySpinner(text, duration = 0) {
|
||||
// Remove existing overlay if any
|
||||
const existing = document.getElementById('overlay-spinner');
|
||||
if (existing) {
|
||||
// Clear any existing timer
|
||||
if (window.overlaySpinnerTimer) {
|
||||
clearInterval(window.overlaySpinnerTimer);
|
||||
window.overlaySpinnerTimer = null;
|
||||
}
|
||||
existing.remove();
|
||||
}
|
||||
|
||||
// Create overlay
|
||||
const overlay = document.createElement('div');
|
||||
overlay.id = 'overlay-spinner';
|
||||
overlay.className = 'overlay-spinner';
|
||||
|
||||
// Create progress container
|
||||
const progressContainer = document.createElement('div');
|
||||
progressContainer.className = 'progress-container';
|
||||
|
||||
// Create SVG progress ring
|
||||
const radius = 56;
|
||||
const circumference = 2 * Math.PI * radius;
|
||||
|
||||
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
||||
svg.setAttribute('width', '120');
|
||||
svg.setAttribute('height', '120');
|
||||
svg.setAttribute('class', 'progress-ring');
|
||||
|
||||
// Background circle
|
||||
const bgCircle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
|
||||
bgCircle.setAttribute('class', 'progress-ring-bg');
|
||||
bgCircle.setAttribute('cx', '60');
|
||||
bgCircle.setAttribute('cy', '60');
|
||||
bgCircle.setAttribute('r', radius);
|
||||
|
||||
// Progress circle
|
||||
const progressCircle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
|
||||
progressCircle.setAttribute('class', 'progress-ring-circle');
|
||||
progressCircle.setAttribute('cx', '60');
|
||||
progressCircle.setAttribute('cy', '60');
|
||||
progressCircle.setAttribute('r', radius);
|
||||
progressCircle.style.strokeDasharray = circumference;
|
||||
progressCircle.style.strokeDashoffset = circumference;
|
||||
|
||||
svg.appendChild(bgCircle);
|
||||
svg.appendChild(progressCircle);
|
||||
|
||||
// Create progress content (percentage display)
|
||||
const progressContent = document.createElement('div');
|
||||
progressContent.className = 'progress-content';
|
||||
|
||||
const progressPercentage = document.createElement('div');
|
||||
progressPercentage.className = 'progress-percentage';
|
||||
progressPercentage.textContent = '0%';
|
||||
|
||||
progressContent.appendChild(progressPercentage);
|
||||
|
||||
progressContainer.appendChild(svg);
|
||||
progressContainer.appendChild(progressContent);
|
||||
|
||||
// Create text
|
||||
const spinnerText = document.createElement('div');
|
||||
spinnerText.className = 'spinner-text';
|
||||
spinnerText.textContent = text;
|
||||
|
||||
overlay.appendChild(progressContainer);
|
||||
overlay.appendChild(spinnerText);
|
||||
document.body.appendChild(overlay);
|
||||
|
||||
// Animate progress if duration is provided
|
||||
if (duration > 0) {
|
||||
const startTime = Date.now();
|
||||
|
||||
window.overlaySpinnerTimer = setInterval(() => {
|
||||
const elapsed = (Date.now() - startTime) / 1000;
|
||||
const progress = Math.min(elapsed / duration, 1);
|
||||
const percentage = Math.round(progress * 100);
|
||||
|
||||
// Update progress ring
|
||||
const offset = circumference - (progress * circumference);
|
||||
progressCircle.style.strokeDashoffset = offset;
|
||||
|
||||
// Update percentage display
|
||||
progressPercentage.textContent = `${percentage}%`;
|
||||
|
||||
// Stop timer if complete
|
||||
if (progress >= 1) {
|
||||
clearInterval(window.overlaySpinnerTimer);
|
||||
window.overlaySpinnerTimer = null;
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
|
||||
// Hide full-page overlay spinner
|
||||
function hideOverlaySpinner() {
|
||||
// Clear timer if exists
|
||||
if (window.overlaySpinnerTimer) {
|
||||
clearInterval(window.overlaySpinnerTimer);
|
||||
window.overlaySpinnerTimer = null;
|
||||
}
|
||||
|
||||
const overlay = document.getElementById('overlay-spinner');
|
||||
if (overlay) overlay.remove();
|
||||
}
|
||||
|
||||
// Update capture duration and save to localStorage
|
||||
function updateCaptureDuration(value) {
|
||||
document.getElementById('test-template-duration-value').textContent = value;
|
||||
@@ -2546,26 +2655,8 @@ async function runTemplateTest() {
|
||||
const template = window.currentTestingTemplate;
|
||||
const resultsDiv = document.getElementById('test-template-results');
|
||||
|
||||
// Show loading state without destroying the structure
|
||||
const loadingDiv = document.createElement('div');
|
||||
loadingDiv.className = 'loading';
|
||||
loadingDiv.textContent = t('templates.test.running');
|
||||
loadingDiv.style.position = 'absolute';
|
||||
loadingDiv.style.inset = '0';
|
||||
loadingDiv.style.background = 'var(--bg-primary)';
|
||||
loadingDiv.style.display = 'flex';
|
||||
loadingDiv.style.alignItems = 'center';
|
||||
loadingDiv.style.justifyContent = 'center';
|
||||
loadingDiv.style.zIndex = '10';
|
||||
loadingDiv.id = 'test-loading-overlay';
|
||||
|
||||
// Remove old loading overlay if exists
|
||||
const oldLoading = document.getElementById('test-loading-overlay');
|
||||
if (oldLoading) oldLoading.remove();
|
||||
|
||||
resultsDiv.style.display = 'block';
|
||||
resultsDiv.style.position = 'relative';
|
||||
resultsDiv.appendChild(loadingDiv);
|
||||
// Show full-page overlay spinner with progress
|
||||
showOverlaySpinner(t('templates.test.running'), captureDuration);
|
||||
|
||||
try {
|
||||
const response = await fetchWithAuth('/capture-templates/test', {
|
||||
@@ -2587,9 +2678,8 @@ async function runTemplateTest() {
|
||||
displayTestResults(result);
|
||||
} catch (error) {
|
||||
console.error('Error running test:', error);
|
||||
// Remove loading overlay
|
||||
const loadingOverlay = document.getElementById('test-loading-overlay');
|
||||
if (loadingOverlay) loadingOverlay.remove();
|
||||
// Hide overlay spinner
|
||||
hideOverlaySpinner();
|
||||
// Show short error in snack, details are in console
|
||||
showToast(t('templates.test.error.failed'), 'error');
|
||||
}
|
||||
@@ -2599,9 +2689,8 @@ async function runTemplateTest() {
|
||||
function displayTestResults(result) {
|
||||
const resultsDiv = document.getElementById('test-template-results');
|
||||
|
||||
// Remove loading overlay
|
||||
const loadingOverlay = document.getElementById('test-loading-overlay');
|
||||
if (loadingOverlay) loadingOverlay.remove();
|
||||
// Hide overlay spinner
|
||||
hideOverlaySpinner();
|
||||
|
||||
// Full capture preview
|
||||
const previewImg = document.getElementById('test-template-preview-image');
|
||||
|
||||
Reference in New Issue
Block a user