Add common.loading locale key and cancellable capture test overlay
Add missing common.loading i18n key to en/ru locales. Add close button and ESC key support to the overlay spinner so users can cancel running capture tests. Uses AbortController to abort the in-flight fetch request. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -163,10 +163,28 @@ export function showOverlaySpinner(text, duration = 0) {
|
||||
existing.remove();
|
||||
}
|
||||
|
||||
// AbortController for cancelling in-flight fetch
|
||||
window._overlayAbortController = new AbortController();
|
||||
|
||||
const overlay = document.createElement('div');
|
||||
overlay.id = 'overlay-spinner';
|
||||
overlay.className = 'overlay-spinner';
|
||||
|
||||
// Close button
|
||||
const closeBtn = document.createElement('button');
|
||||
closeBtn.className = 'overlay-spinner-close';
|
||||
closeBtn.innerHTML = '×';
|
||||
closeBtn.title = 'Cancel';
|
||||
closeBtn.onclick = () => hideOverlaySpinner();
|
||||
overlay.appendChild(closeBtn);
|
||||
|
||||
// ESC key handler
|
||||
function onEsc(e) {
|
||||
if (e.key === 'Escape') hideOverlaySpinner();
|
||||
}
|
||||
window._overlayEscHandler = onEsc;
|
||||
document.addEventListener('keydown', onEsc);
|
||||
|
||||
const progressContainer = document.createElement('div');
|
||||
progressContainer.className = 'progress-container';
|
||||
|
||||
@@ -235,6 +253,14 @@ export function hideOverlaySpinner() {
|
||||
clearInterval(window.overlaySpinnerTimer);
|
||||
window.overlaySpinnerTimer = null;
|
||||
}
|
||||
if (window._overlayEscHandler) {
|
||||
document.removeEventListener('keydown', window._overlayEscHandler);
|
||||
window._overlayEscHandler = null;
|
||||
}
|
||||
if (window._overlayAbortController) {
|
||||
window._overlayAbortController.abort();
|
||||
window._overlayAbortController = null;
|
||||
}
|
||||
const overlay = document.getElementById('overlay-spinner');
|
||||
if (overlay) overlay.remove();
|
||||
}
|
||||
|
||||
@@ -298,6 +298,7 @@ export async function runTemplateTest() {
|
||||
|
||||
const template = window.currentTestingTemplate;
|
||||
showOverlaySpinner(t('templates.test.running'), captureDuration);
|
||||
const signal = window._overlayAbortController?.signal;
|
||||
|
||||
try {
|
||||
const response = await fetchWithAuth('/capture-templates/test', {
|
||||
@@ -307,7 +308,8 @@ export async function runTemplateTest() {
|
||||
engine_config: template.engine_config,
|
||||
display_index: parseInt(displayIndex),
|
||||
capture_duration: captureDuration
|
||||
})
|
||||
}),
|
||||
signal
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
@@ -319,6 +321,7 @@ export async function runTemplateTest() {
|
||||
localStorage.setItem('lastTestDisplayIndex', displayIndex);
|
||||
displayTestResults(result);
|
||||
} catch (error) {
|
||||
if (error.name === 'AbortError') return;
|
||||
console.error('Error running test:', error);
|
||||
hideOverlaySpinner();
|
||||
showToast(t('templates.test.error.failed'), 'error');
|
||||
@@ -980,11 +983,13 @@ export async function runStreamTest() {
|
||||
if (!_currentTestStreamId) return;
|
||||
const captureDuration = parseFloat(document.getElementById('test-stream-duration').value);
|
||||
showOverlaySpinner(t('streams.test.running'), captureDuration);
|
||||
const signal = window._overlayAbortController?.signal;
|
||||
|
||||
try {
|
||||
const response = await fetchWithAuth(`/picture-sources/${_currentTestStreamId}/test`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ capture_duration: captureDuration })
|
||||
body: JSON.stringify({ capture_duration: captureDuration }),
|
||||
signal
|
||||
});
|
||||
if (!response.ok) {
|
||||
const error = await response.json();
|
||||
@@ -995,6 +1000,7 @@ export async function runStreamTest() {
|
||||
const fullImageSrc = result.full_capture.full_image || result.full_capture.image;
|
||||
openLightbox(fullImageSrc, buildTestStatsHtml(result));
|
||||
} catch (error) {
|
||||
if (error.name === 'AbortError') return;
|
||||
console.error('Error running stream test:', error);
|
||||
hideOverlaySpinner();
|
||||
showToast(t('streams.test.error.failed') + ': ' + error.message, 'error');
|
||||
@@ -1057,11 +1063,13 @@ export async function runPPTemplateTest() {
|
||||
|
||||
const captureDuration = parseFloat(document.getElementById('test-pp-duration').value);
|
||||
showOverlaySpinner(t('postprocessing.test.running'), captureDuration);
|
||||
const signal = window._overlayAbortController?.signal;
|
||||
|
||||
try {
|
||||
const response = await fetchWithAuth(`/postprocessing-templates/${_currentTestPPTemplateId}/test`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ source_stream_id: sourceStreamId, capture_duration: captureDuration })
|
||||
body: JSON.stringify({ source_stream_id: sourceStreamId, capture_duration: captureDuration }),
|
||||
signal
|
||||
});
|
||||
if (!response.ok) {
|
||||
const error = await response.json();
|
||||
@@ -1072,6 +1080,7 @@ export async function runPPTemplateTest() {
|
||||
const fullImageSrc = result.full_capture.full_image || result.full_capture.image;
|
||||
openLightbox(fullImageSrc, buildTestStatsHtml(result));
|
||||
} catch (error) {
|
||||
if (error.name === 'AbortError') return;
|
||||
console.error('Error running PP template test:', error);
|
||||
hideOverlaySpinner();
|
||||
showToast(t('postprocessing.test.error.failed') + ': ' + error.message, 'error');
|
||||
|
||||
@@ -237,6 +237,7 @@
|
||||
"confirm.title": "Confirm Action",
|
||||
"confirm.yes": "Yes",
|
||||
"confirm.no": "No",
|
||||
"common.loading": "Loading...",
|
||||
"common.delete": "Delete",
|
||||
"common.edit": "Edit",
|
||||
"streams.title": "\uD83D\uDCFA Sources",
|
||||
|
||||
@@ -237,6 +237,7 @@
|
||||
"confirm.title": "Подтверждение Действия",
|
||||
"confirm.yes": "Да",
|
||||
"confirm.no": "Нет",
|
||||
"common.loading": "Загрузка...",
|
||||
"common.delete": "Удалить",
|
||||
"common.edit": "Редактировать",
|
||||
"streams.title": "\uD83D\uDCFA Источники",
|
||||
|
||||
@@ -1006,6 +1006,24 @@ input:-webkit-autofill:focus {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.overlay-spinner-close {
|
||||
position: absolute;
|
||||
top: 16px;
|
||||
right: 16px;
|
||||
background: none;
|
||||
border: none;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
font-size: 32px;
|
||||
cursor: pointer;
|
||||
line-height: 1;
|
||||
padding: 4px 8px;
|
||||
transition: color 0.15s;
|
||||
}
|
||||
|
||||
.overlay-spinner-close:hover {
|
||||
color: white;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user