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();
|
existing.remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AbortController for cancelling in-flight fetch
|
||||||
|
window._overlayAbortController = new AbortController();
|
||||||
|
|
||||||
const overlay = document.createElement('div');
|
const overlay = document.createElement('div');
|
||||||
overlay.id = 'overlay-spinner';
|
overlay.id = 'overlay-spinner';
|
||||||
overlay.className = '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');
|
const progressContainer = document.createElement('div');
|
||||||
progressContainer.className = 'progress-container';
|
progressContainer.className = 'progress-container';
|
||||||
|
|
||||||
@@ -235,6 +253,14 @@ export function hideOverlaySpinner() {
|
|||||||
clearInterval(window.overlaySpinnerTimer);
|
clearInterval(window.overlaySpinnerTimer);
|
||||||
window.overlaySpinnerTimer = null;
|
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');
|
const overlay = document.getElementById('overlay-spinner');
|
||||||
if (overlay) overlay.remove();
|
if (overlay) overlay.remove();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -298,6 +298,7 @@ export async function runTemplateTest() {
|
|||||||
|
|
||||||
const template = window.currentTestingTemplate;
|
const template = window.currentTestingTemplate;
|
||||||
showOverlaySpinner(t('templates.test.running'), captureDuration);
|
showOverlaySpinner(t('templates.test.running'), captureDuration);
|
||||||
|
const signal = window._overlayAbortController?.signal;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetchWithAuth('/capture-templates/test', {
|
const response = await fetchWithAuth('/capture-templates/test', {
|
||||||
@@ -307,7 +308,8 @@ export async function runTemplateTest() {
|
|||||||
engine_config: template.engine_config,
|
engine_config: template.engine_config,
|
||||||
display_index: parseInt(displayIndex),
|
display_index: parseInt(displayIndex),
|
||||||
capture_duration: captureDuration
|
capture_duration: captureDuration
|
||||||
})
|
}),
|
||||||
|
signal
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
@@ -319,6 +321,7 @@ export async function runTemplateTest() {
|
|||||||
localStorage.setItem('lastTestDisplayIndex', displayIndex);
|
localStorage.setItem('lastTestDisplayIndex', displayIndex);
|
||||||
displayTestResults(result);
|
displayTestResults(result);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
if (error.name === 'AbortError') return;
|
||||||
console.error('Error running test:', error);
|
console.error('Error running test:', error);
|
||||||
hideOverlaySpinner();
|
hideOverlaySpinner();
|
||||||
showToast(t('templates.test.error.failed'), 'error');
|
showToast(t('templates.test.error.failed'), 'error');
|
||||||
@@ -980,11 +983,13 @@ export async function runStreamTest() {
|
|||||||
if (!_currentTestStreamId) return;
|
if (!_currentTestStreamId) return;
|
||||||
const captureDuration = parseFloat(document.getElementById('test-stream-duration').value);
|
const captureDuration = parseFloat(document.getElementById('test-stream-duration').value);
|
||||||
showOverlaySpinner(t('streams.test.running'), captureDuration);
|
showOverlaySpinner(t('streams.test.running'), captureDuration);
|
||||||
|
const signal = window._overlayAbortController?.signal;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetchWithAuth(`/picture-sources/${_currentTestStreamId}/test`, {
|
const response = await fetchWithAuth(`/picture-sources/${_currentTestStreamId}/test`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: JSON.stringify({ capture_duration: captureDuration })
|
body: JSON.stringify({ capture_duration: captureDuration }),
|
||||||
|
signal
|
||||||
});
|
});
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const error = await response.json();
|
const error = await response.json();
|
||||||
@@ -995,6 +1000,7 @@ export async function runStreamTest() {
|
|||||||
const fullImageSrc = result.full_capture.full_image || result.full_capture.image;
|
const fullImageSrc = result.full_capture.full_image || result.full_capture.image;
|
||||||
openLightbox(fullImageSrc, buildTestStatsHtml(result));
|
openLightbox(fullImageSrc, buildTestStatsHtml(result));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
if (error.name === 'AbortError') return;
|
||||||
console.error('Error running stream test:', error);
|
console.error('Error running stream test:', error);
|
||||||
hideOverlaySpinner();
|
hideOverlaySpinner();
|
||||||
showToast(t('streams.test.error.failed') + ': ' + error.message, 'error');
|
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);
|
const captureDuration = parseFloat(document.getElementById('test-pp-duration').value);
|
||||||
showOverlaySpinner(t('postprocessing.test.running'), captureDuration);
|
showOverlaySpinner(t('postprocessing.test.running'), captureDuration);
|
||||||
|
const signal = window._overlayAbortController?.signal;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetchWithAuth(`/postprocessing-templates/${_currentTestPPTemplateId}/test`, {
|
const response = await fetchWithAuth(`/postprocessing-templates/${_currentTestPPTemplateId}/test`, {
|
||||||
method: 'POST',
|
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) {
|
if (!response.ok) {
|
||||||
const error = await response.json();
|
const error = await response.json();
|
||||||
@@ -1072,6 +1080,7 @@ export async function runPPTemplateTest() {
|
|||||||
const fullImageSrc = result.full_capture.full_image || result.full_capture.image;
|
const fullImageSrc = result.full_capture.full_image || result.full_capture.image;
|
||||||
openLightbox(fullImageSrc, buildTestStatsHtml(result));
|
openLightbox(fullImageSrc, buildTestStatsHtml(result));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
if (error.name === 'AbortError') return;
|
||||||
console.error('Error running PP template test:', error);
|
console.error('Error running PP template test:', error);
|
||||||
hideOverlaySpinner();
|
hideOverlaySpinner();
|
||||||
showToast(t('postprocessing.test.error.failed') + ': ' + error.message, 'error');
|
showToast(t('postprocessing.test.error.failed') + ': ' + error.message, 'error');
|
||||||
|
|||||||
@@ -237,6 +237,7 @@
|
|||||||
"confirm.title": "Confirm Action",
|
"confirm.title": "Confirm Action",
|
||||||
"confirm.yes": "Yes",
|
"confirm.yes": "Yes",
|
||||||
"confirm.no": "No",
|
"confirm.no": "No",
|
||||||
|
"common.loading": "Loading...",
|
||||||
"common.delete": "Delete",
|
"common.delete": "Delete",
|
||||||
"common.edit": "Edit",
|
"common.edit": "Edit",
|
||||||
"streams.title": "\uD83D\uDCFA Sources",
|
"streams.title": "\uD83D\uDCFA Sources",
|
||||||
|
|||||||
@@ -237,6 +237,7 @@
|
|||||||
"confirm.title": "Подтверждение Действия",
|
"confirm.title": "Подтверждение Действия",
|
||||||
"confirm.yes": "Да",
|
"confirm.yes": "Да",
|
||||||
"confirm.no": "Нет",
|
"confirm.no": "Нет",
|
||||||
|
"common.loading": "Загрузка...",
|
||||||
"common.delete": "Удалить",
|
"common.delete": "Удалить",
|
||||||
"common.edit": "Редактировать",
|
"common.edit": "Редактировать",
|
||||||
"streams.title": "\uD83D\uDCFA Источники",
|
"streams.title": "\uD83D\uDCFA Источники",
|
||||||
|
|||||||
@@ -1006,6 +1006,24 @@ input:-webkit-autofill:focus {
|
|||||||
font-weight: 500;
|
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 {
|
@keyframes spin {
|
||||||
to { transform: rotate(360deg); }
|
to { transform: rotate(360deg); }
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user