Speed up camera source modal with cached enumeration and instant open
Cache camera enumeration results for 30s and limit probe range using WMI camera count on Windows. Open source modal instantly with a loading spinner while dropdowns are populated asynchronously. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -246,6 +246,25 @@ input:-webkit-autofill:focus {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
/* Modal body loading overlay — hides form while data loads */
|
||||
.modal-body-loading {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 48px 20px;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.modal-body-loading .loading-spinner {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.modal-body-loading-text {
|
||||
color: var(--text-secondary);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
/* Full-page overlay spinner */
|
||||
.overlay-spinner {
|
||||
position: fixed;
|
||||
|
||||
@@ -1494,6 +1494,10 @@ export async function showAddStreamModal(presetType, cloneData = null) {
|
||||
document.getElementById('stream-source').onchange = () => _autoGenerateStreamName();
|
||||
document.getElementById('stream-pp-template').onchange = () => _autoGenerateStreamName();
|
||||
|
||||
// Open modal instantly with loading indicator
|
||||
_showStreamModalLoading(true);
|
||||
streamModal.open();
|
||||
|
||||
await populateStreamModalDropdowns();
|
||||
|
||||
// Pre-fill from clone data after dropdowns are populated
|
||||
@@ -1518,12 +1522,19 @@ export async function showAddStreamModal(presetType, cloneData = null) {
|
||||
}
|
||||
}
|
||||
|
||||
streamModal.open();
|
||||
_showStreamModalLoading(false);
|
||||
streamModal.snapshot();
|
||||
}
|
||||
|
||||
export async function editStream(streamId) {
|
||||
try {
|
||||
// Open modal instantly with loading indicator
|
||||
document.getElementById('stream-modal-title').innerHTML = t('streams.edit');
|
||||
document.getElementById('stream-form').reset();
|
||||
document.getElementById('stream-error').style.display = 'none';
|
||||
_showStreamModalLoading(true);
|
||||
streamModal.open();
|
||||
|
||||
const response = await fetchWithAuth(`/picture-sources/${streamId}`);
|
||||
if (!response.ok) throw new Error(`Failed to load stream: ${response.status}`);
|
||||
const stream = await response.json();
|
||||
@@ -1533,7 +1544,6 @@ export async function editStream(streamId) {
|
||||
document.getElementById('stream-id').value = streamId;
|
||||
document.getElementById('stream-name').value = stream.name;
|
||||
document.getElementById('stream-description').value = stream.description || '';
|
||||
document.getElementById('stream-error').style.display = 'none';
|
||||
|
||||
document.getElementById('stream-type').value = stream.stream_type;
|
||||
set_lastValidatedImageSource('');
|
||||
@@ -1565,10 +1575,11 @@ export async function editStream(streamId) {
|
||||
if (stream.image_source) validateStaticImage();
|
||||
}
|
||||
|
||||
streamModal.open();
|
||||
_showStreamModalLoading(false);
|
||||
streamModal.snapshot();
|
||||
} catch (error) {
|
||||
console.error('Error loading stream:', error);
|
||||
streamModal.forceClose();
|
||||
showToast(t('streams.error.load') + ': ' + error.message, 'error');
|
||||
}
|
||||
}
|
||||
@@ -1751,6 +1762,16 @@ export async function deleteStream(streamId) {
|
||||
}
|
||||
}
|
||||
|
||||
/** Toggle loading overlay in stream modal — hides form while data loads. */
|
||||
function _showStreamModalLoading(show) {
|
||||
const loading = document.getElementById('stream-modal-loading');
|
||||
const form = document.getElementById('stream-form');
|
||||
const footer = document.querySelector('#stream-modal .modal-footer');
|
||||
if (loading) loading.style.display = show ? '' : 'none';
|
||||
if (form) form.style.display = show ? 'none' : '';
|
||||
if (footer) footer.style.visibility = show ? 'hidden' : '';
|
||||
}
|
||||
|
||||
export async function closeStreamModal() {
|
||||
await streamModal.close();
|
||||
}
|
||||
|
||||
@@ -334,6 +334,7 @@
|
||||
"streams.updated": "Source updated successfully",
|
||||
"streams.deleted": "Source deleted successfully",
|
||||
"streams.delete.confirm": "Are you sure you want to delete this source?",
|
||||
"streams.modal.loading": "Loading...",
|
||||
"streams.error.load": "Failed to load sources",
|
||||
"streams.error.required": "Please fill in all required fields",
|
||||
"streams.error.delete": "Failed to delete source",
|
||||
|
||||
@@ -334,6 +334,7 @@
|
||||
"streams.updated": "Источник успешно обновлён",
|
||||
"streams.deleted": "Источник успешно удалён",
|
||||
"streams.delete.confirm": "Вы уверены, что хотите удалить этот источник?",
|
||||
"streams.modal.loading": "Загрузка...",
|
||||
"streams.error.load": "Не удалось загрузить источники",
|
||||
"streams.error.required": "Пожалуйста, заполните все обязательные поля",
|
||||
"streams.error.delete": "Не удалось удалить источник",
|
||||
|
||||
@@ -334,6 +334,7 @@
|
||||
"streams.updated": "源更新成功",
|
||||
"streams.deleted": "源删除成功",
|
||||
"streams.delete.confirm": "确定要删除此源吗?",
|
||||
"streams.modal.loading": "加载中...",
|
||||
"streams.error.load": "加载源失败",
|
||||
"streams.error.required": "请填写所有必填项",
|
||||
"streams.error.delete": "删除源失败",
|
||||
|
||||
Reference in New Issue
Block a user