Add reusable DataCache class, unify frontend cache patterns

- Create DataCache class with fetch deduplication, invalidation, subscribers
- Instantiate 10 cache instances in state.js (streams, templates, sources, etc.)
- Replace inline fetch+parse+set patterns with cache.fetch() calls across modules
- Eliminate dual _scenesCache/_presetsCache sync via shared scenePresetsCache
- Remove 9 now-unused setter functions from state.js
- Clean up unused setter imports from audio-sources, value-sources, displays

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-28 19:35:20 +03:00
parent ff4e7f8adb
commit a34edf9650
11 changed files with 220 additions and 176 deletions

View File

@@ -3,11 +3,12 @@
*/
import {
_cachedDisplays, set_cachedDisplays,
_cachedStreams, set_cachedStreams,
_cachedPPTemplates, set_cachedPPTemplates,
_cachedCaptureTemplates, set_cachedCaptureTemplates,
_availableFilters, set_availableFilters,
_cachedDisplays,
displaysCache,
_cachedStreams,
_cachedPPTemplates,
_cachedCaptureTemplates,
_availableFilters,
availableEngines, setAvailableEngines,
currentEditingTemplateId, setCurrentEditingTemplateId,
_templateNameManuallyEdited, set_templateNameManuallyEdited,
@@ -18,14 +19,16 @@ import {
_currentTestStreamId, set_currentTestStreamId,
_currentTestPPTemplateId, set_currentTestPPTemplateId,
_lastValidatedImageSource, set_lastValidatedImageSource,
_cachedAudioSources, set_cachedAudioSources,
_cachedValueSources, set_cachedValueSources,
_cachedAudioTemplates, set_cachedAudioTemplates,
_cachedAudioSources,
_cachedValueSources,
_cachedAudioTemplates,
availableAudioEngines, setAvailableAudioEngines,
currentEditingAudioTemplateId, setCurrentEditingAudioTemplateId,
_audioTemplateNameManuallyEdited, set_audioTemplateNameManuallyEdited,
_sourcesLoading, set_sourcesLoading,
apiKey,
streamsCache, ppTemplatesCache, captureTemplatesCache,
audioSourcesCache, audioTemplatesCache, valueSourcesCache, filtersCache,
} from '../core/state.js';
import { API_BASE, getHeaders, fetchWithAuth, escapeHtml } from '../core/api.js';
import { t } from '../core/i18n.js';
@@ -153,10 +156,7 @@ const audioTemplateModal = new AudioTemplateModal();
async function loadCaptureTemplates() {
try {
const response = await fetchWithAuth('/capture-templates');
if (!response.ok) throw new Error(`Failed to load templates: ${response.status}`);
const data = await response.json();
set_cachedCaptureTemplates(data.templates || []);
await captureTemplatesCache.fetch();
renderPictureSourcesList(_cachedStreams);
} catch (error) {
if (error.isAuth) return;
@@ -397,7 +397,7 @@ async function loadDisplaysForTest() {
const response = await fetchWithAuth(url);
if (!response.ok) throw new Error(`Failed to load displays: ${response.status}`);
const displaysData = await response.json();
set_cachedDisplays(displaysData.displays || []);
displaysCache.update(displaysData.displays || []);
}
let selectedIndex = null;
@@ -745,10 +745,7 @@ function collectAudioEngineConfig() {
async function loadAudioTemplates() {
try {
const response = await fetchWithAuth('/audio-templates');
if (!response.ok) throw new Error(`Failed to load audio templates: ${response.status}`);
const data = await response.json();
set_cachedAudioTemplates(data.templates || []);
await audioTemplatesCache.fetch();
renderPictureSourcesList(_cachedStreams);
} catch (error) {
if (error.isAuth) return;
@@ -1096,44 +1093,16 @@ export async function loadPictureSources() {
set_sourcesLoading(true);
setTabRefreshing('streams-list', true);
try {
const [filtersResp, ppResp, captResp, streamsResp, audioResp, valueResp, audioTplResp] = await Promise.all([
_availableFilters.length === 0 ? fetchWithAuth('/filters') : Promise.resolve(null),
fetchWithAuth('/postprocessing-templates'),
fetchWithAuth('/capture-templates'),
fetchWithAuth('/picture-sources'),
fetchWithAuth('/audio-sources'),
fetchWithAuth('/value-sources'),
fetchWithAuth('/audio-templates'),
const [streams] = await Promise.all([
streamsCache.fetch(),
ppTemplatesCache.fetch(),
captureTemplatesCache.fetch(),
audioSourcesCache.fetch(),
valueSourcesCache.fetch(),
audioTemplatesCache.fetch(),
filtersCache.data.length === 0 ? filtersCache.fetch() : Promise.resolve(filtersCache.data),
]);
if (filtersResp && filtersResp.ok) {
const fd = await filtersResp.json();
set_availableFilters(fd.filters || []);
}
if (ppResp.ok) {
const pd = await ppResp.json();
set_cachedPPTemplates(pd.templates || []);
}
if (captResp.ok) {
const cd = await captResp.json();
set_cachedCaptureTemplates(cd.templates || []);
}
if (audioResp && audioResp.ok) {
const ad = await audioResp.json();
set_cachedAudioSources(ad.sources || []);
}
if (valueResp && valueResp.ok) {
const vd = await valueResp.json();
set_cachedValueSources(vd.sources || []);
}
if (audioTplResp && audioTplResp.ok) {
const atd = await audioTplResp.json();
set_cachedAudioTemplates(atd.templates || []);
}
if (!streamsResp.ok) throw new Error(`Failed to load streams: ${streamsResp.status}`);
const data = await streamsResp.json();
set_cachedStreams(data.streams || []);
renderPictureSourcesList(_cachedStreams);
renderPictureSourcesList(streams);
} catch (error) {
if (error.isAuth) return;
console.error('Error loading picture sources:', error);
@@ -1576,7 +1545,7 @@ async function populateStreamModalDropdowns() {
if (displaysRes.ok) {
const displaysData = await displaysRes.json();
set_cachedDisplays(displaysData.displays || []);
displaysCache.update(displaysData.displays || []);
}
_streamModalDisplaysEngine = null; // desktop displays loaded
@@ -1660,7 +1629,7 @@ async function _refreshStreamDisplaysForEngine(engineType) {
const resp = await fetchWithAuth(url);
if (resp.ok) {
const data = await resp.json();
set_cachedDisplays(data.displays || []);
displaysCache.update(data.displays || []);
}
} catch (error) {
console.error('Error refreshing displays for engine:', error);
@@ -1841,8 +1810,7 @@ export async function showTestPPTemplateModal(templateId) {
select.innerHTML = '';
if (_cachedStreams.length === 0) {
try {
const resp = await fetchWithAuth('/picture-sources');
if (resp.ok) { const d = await resp.json(); set_cachedStreams(d.streams || []); }
await streamsCache.fetch();
} catch (e) { console.warn('Could not load streams for PP test:', e); }
}
for (const s of _cachedStreams) {
@@ -1894,24 +1862,13 @@ export function runPPTemplateTest() {
// ===== PP Templates =====
async function loadAvailableFilters() {
try {
const response = await fetchWithAuth('/filters');
if (!response.ok) throw new Error(`Failed to load filters: ${response.status}`);
const data = await response.json();
set_availableFilters(data.filters || []);
} catch (error) {
console.error('Error loading available filters:', error);
set_availableFilters([]);
}
await filtersCache.fetch();
}
async function loadPPTemplates() {
try {
if (_availableFilters.length === 0) await loadAvailableFilters();
const response = await fetchWithAuth('/postprocessing-templates');
if (!response.ok) throw new Error(`Failed to load templates: ${response.status}`);
const data = await response.json();
set_cachedPPTemplates(data.templates || []);
if (_availableFilters.length === 0) await filtersCache.fetch();
await ppTemplatesCache.fetch();
renderPictureSourcesList(_cachedStreams);
} catch (error) {
console.error('Error loading PP templates:', error);