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:
2026-03-01 13:35:26 +03:00
parent 9ee6dcf94a
commit ddfa7637d6
7 changed files with 76 additions and 4 deletions

View File

@@ -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();
}