refactor(types): migrate (window as any) statics to typed window globals
59 sites across 19 feature modules switched from `(window as any).foo` to the typed `window.foo` form against global-types.d.ts. The 7 remaining sites use dynamic string indexing (`window[fnName]`) where a typed access is impossible — those keep the narrow cast and are documented as the legitimate exception in the typedef file's header. global-types.d.ts grows entries for `loadIntegrations`, `loadUpdateSettings`, `loadUpdateStatus`, `initUpdateSettingsPanel`, `onTestDisplaySelected`, `openSettingsModal`, `renderAboutPanel`, `switchSettingsTab`. The `applyAccentColor` signature is widened to accept the (accent, persist) call shape observed at the appearance preset call site so tsc validates the real contract.
This commit is contained in:
@@ -804,7 +804,7 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||
startConnectionMonitor();
|
||||
|
||||
// Expose auth state for inline scripts (after loadServerInfo sets it)
|
||||
(window as any)._authRequired = authRequired;
|
||||
window._authRequired = authRequired;
|
||||
if (typeof window.updateAuthUI === 'function') window.updateAuthUI();
|
||||
|
||||
// Server is unconfigured for LAN access → setup screen already shown by
|
||||
|
||||
@@ -312,21 +312,21 @@ export async function loadServerInfo() {
|
||||
// Auth mode detection
|
||||
const authNeeded = data.auth_required !== false;
|
||||
setAuthRequired(authNeeded);
|
||||
(window as any)._authRequired = authNeeded;
|
||||
window._authRequired = authNeeded;
|
||||
|
||||
// Setup-required detection (LAN client + no keys configured server-side).
|
||||
// When true, no API key will ever succeed — show a dedicated screen
|
||||
// instead of the login form.
|
||||
const setupNeeded = data.setup_required === true;
|
||||
setSetupRequired(setupNeeded);
|
||||
(window as any)._setupRequired = setupNeeded;
|
||||
window._setupRequired = setupNeeded;
|
||||
if (setupNeeded) {
|
||||
if (typeof window.showSetupRequiredModal === 'function') {
|
||||
window.showSetupRequiredModal();
|
||||
}
|
||||
} else if (typeof window.hideSetupRequiredModal === 'function') {
|
||||
// Server was reconfigured — clear the setup overlay if it was up.
|
||||
if ((window as any)._setupModalOpen) window.hideSetupRequiredModal();
|
||||
if (window._setupModalOpen) window.hideSetupRequiredModal();
|
||||
}
|
||||
|
||||
// Project URLs (repo, donate)
|
||||
|
||||
@@ -177,7 +177,7 @@ function _colorPopoverHtml(pickerId: string, currentColor: string): string {
|
||||
function getCardColorAriaLabel(): string {
|
||||
// i18n is not always loaded when this module evaluates (renders
|
||||
// happen during early page boot). Fall back to English.
|
||||
return (window as any).__t?.('common.card_color') || 'Card color';
|
||||
return window.__t?.('common.card_color') || 'Card color';
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -315,7 +315,7 @@ export function applyStylePreset(id: string): void {
|
||||
document.documentElement.style.setProperty('--font-heading', preset.fontHeading);
|
||||
|
||||
// Apply accent color via existing mechanism
|
||||
const applyAccent = (window as any).applyAccentColor;
|
||||
const applyAccent = window.applyAccentColor;
|
||||
if (typeof applyAccent === 'function') {
|
||||
applyAccent(preset.accent, true);
|
||||
} else {
|
||||
|
||||
@@ -38,8 +38,8 @@ registerIconEntityType('automation', makeSimpleIconAdapter<Automation>({
|
||||
endpointPrefix: '/automations',
|
||||
reload: async () => {
|
||||
automationsCacheObj.invalidate();
|
||||
if (typeof (window as any).loadAutomations === 'function') {
|
||||
await (window as any).loadAutomations();
|
||||
if (typeof window.loadAutomations === 'function') {
|
||||
await window.loadAutomations();
|
||||
}
|
||||
},
|
||||
typeLabelKey: 'device.icon.entity.automation',
|
||||
@@ -653,7 +653,7 @@ export async function openAutomationEditor(automationId?: any, cloneData?: any)
|
||||
// Auto-name wiring
|
||||
_autoNameManuallyEdited = !!(automationId || cloneData);
|
||||
nameInput.oninput = () => { _autoNameManuallyEdited = true; };
|
||||
(window as any)._autoGenerateAutomationName = _autoGenerateAutomationName;
|
||||
window._autoGenerateAutomationName = _autoGenerateAutomationName;
|
||||
if (!automationId && !cloneData) _autoGenerateAutomationName();
|
||||
|
||||
automationModal.open();
|
||||
@@ -869,7 +869,7 @@ function addAutomationRuleRow(rule: any) {
|
||||
removeBtn.addEventListener('click', () => {
|
||||
_disposeHTTPPollWidgets(container);
|
||||
row.remove();
|
||||
const autoGen = (window as any)._autoGenerateAutomationName;
|
||||
const autoGen = window._autoGenerateAutomationName;
|
||||
if (typeof autoGen === 'function') autoGen();
|
||||
});
|
||||
|
||||
|
||||
@@ -31,8 +31,8 @@ registerIconEntityType('color_strip_source', makeSimpleIconAdapter<ColorStripSou
|
||||
endpointPrefix: '/color-strip-sources',
|
||||
reload: async () => {
|
||||
colorStripSourcesCache.invalidate();
|
||||
if (typeof (window as any).loadPictureSources === 'function') {
|
||||
await (window as any).loadPictureSources();
|
||||
if (typeof window.loadPictureSources === 'function') {
|
||||
await window.loadPictureSources();
|
||||
}
|
||||
},
|
||||
typeLabelKey: 'device.icon.entity.color_strip_source',
|
||||
|
||||
@@ -263,7 +263,7 @@ function _openKCRegionEditor(): void {
|
||||
});
|
||||
}
|
||||
|
||||
(window as any)._openKCRegionEditor = _openKCRegionEditor;
|
||||
window._openKCRegionEditor = _openKCRegionEditor;
|
||||
|
||||
async function configureKCRegions(sourceId: string): Promise<void> {
|
||||
try {
|
||||
@@ -296,7 +296,7 @@ async function configureKCRegions(sourceId: string): Promise<void> {
|
||||
showToast(e.message, 'error');
|
||||
}
|
||||
}
|
||||
(window as any).configureKCRegions = configureKCRegions;
|
||||
window.configureKCRegions = configureKCRegions;
|
||||
|
||||
// ══════════════════════════════════════════════════════════════════
|
||||
// Type selector
|
||||
|
||||
@@ -32,8 +32,8 @@ registerIconEntityType('game_integration', makeSimpleIconAdapter<GameIntegration
|
||||
endpointPrefix: '/game-integrations',
|
||||
reload: async () => {
|
||||
gameIntegrationsCache.invalidate();
|
||||
if (typeof (window as any).loadIntegrations === 'function') {
|
||||
await (window as any).loadIntegrations();
|
||||
if (typeof window.loadIntegrations === 'function') {
|
||||
await window.loadIntegrations();
|
||||
}
|
||||
},
|
||||
typeLabelKey: 'device.icon.entity.game_integration',
|
||||
@@ -799,5 +799,5 @@ export async function loadGameIntegrations() {
|
||||
gameAdaptersCache.fetch(),
|
||||
]);
|
||||
// Integrations.ts handles rendering via loadIntegrations
|
||||
if ((window as any).loadIntegrations) (window as any).loadIntegrations();
|
||||
if (window.loadIntegrations) window.loadIntegrations();
|
||||
}
|
||||
|
||||
@@ -23,8 +23,8 @@ registerIconEntityType('ha_source', makeSimpleIconAdapter<HomeAssistantSource>({
|
||||
cache: haSourcesCache,
|
||||
endpointPrefix: '/home-assistant/sources',
|
||||
reload: async () => {
|
||||
if (typeof (window as any).loadIntegrations === 'function') {
|
||||
await (window as any).loadIntegrations();
|
||||
if (typeof window.loadIntegrations === 'function') {
|
||||
await window.loadIntegrations();
|
||||
}
|
||||
},
|
||||
typeLabelKey: 'device.icon.entity.ha_source',
|
||||
@@ -188,7 +188,7 @@ export async function saveHASource(): Promise<void> {
|
||||
showToast(t(id ? 'ha_source.updated' : 'ha_source.created'), 'success');
|
||||
haSourceModal.forceClose();
|
||||
haSourcesCache.invalidate();
|
||||
if (typeof (window as any).loadIntegrations === 'function') await (window as any).loadIntegrations();
|
||||
if (typeof window.loadIntegrations === 'function') await window.loadIntegrations();
|
||||
} catch (e: any) {
|
||||
if (e.isAuth) return;
|
||||
haSourceModal.showError(e.message);
|
||||
@@ -234,7 +234,7 @@ export async function deleteHASource(sourceId: string): Promise<void> {
|
||||
}
|
||||
showToast(t('ha_source.deleted'), 'success');
|
||||
haSourcesCache.invalidate();
|
||||
if (typeof (window as any).loadIntegrations === 'function') await (window as any).loadIntegrations();
|
||||
if (typeof window.loadIntegrations === 'function') await window.loadIntegrations();
|
||||
} catch (e: any) {
|
||||
if (e.isAuth) return;
|
||||
showToast(e.message, 'error');
|
||||
|
||||
@@ -34,8 +34,8 @@ registerIconEntityType('http_endpoint', makeSimpleIconAdapter<HTTPEndpoint>({
|
||||
cache: httpEndpointsCache,
|
||||
endpointPrefix: '/http/endpoints',
|
||||
reload: async () => {
|
||||
if (typeof (window as any).loadIntegrations === 'function') {
|
||||
await (window as any).loadIntegrations();
|
||||
if (typeof window.loadIntegrations === 'function') {
|
||||
await window.loadIntegrations();
|
||||
}
|
||||
},
|
||||
typeLabelKey: 'device.icon.entity.http_endpoint',
|
||||
@@ -329,7 +329,7 @@ export async function saveHTTPEndpoint(): Promise<void> {
|
||||
showToast(t(id ? 'http_endpoint.updated' : 'http_endpoint.created'), 'success');
|
||||
httpEndpointModal.forceClose();
|
||||
httpEndpointsCache.invalidate();
|
||||
if (typeof (window as any).loadIntegrations === 'function') await (window as any).loadIntegrations();
|
||||
if (typeof window.loadIntegrations === 'function') await window.loadIntegrations();
|
||||
} catch (e: any) {
|
||||
if (e.isAuth) return;
|
||||
httpEndpointModal.showError(e.message);
|
||||
@@ -377,7 +377,7 @@ export async function deleteHTTPEndpoint(endpointId: string): Promise<void> {
|
||||
}
|
||||
showToast(t('http_endpoint.deleted'), 'success');
|
||||
httpEndpointsCache.invalidate();
|
||||
if (typeof (window as any).loadIntegrations === 'function') await (window as any).loadIntegrations();
|
||||
if (typeof window.loadIntegrations === 'function') await window.loadIntegrations();
|
||||
} catch (e: any) {
|
||||
if (e.isAuth) return;
|
||||
showToast(e.message, 'error');
|
||||
|
||||
@@ -20,8 +20,8 @@ registerIconEntityType('mqtt_source', makeSimpleIconAdapter<MQTTSource>({
|
||||
cache: mqttSourcesCache,
|
||||
endpointPrefix: '/mqtt/sources',
|
||||
reload: async () => {
|
||||
if (typeof (window as any).loadIntegrations === 'function') {
|
||||
await (window as any).loadIntegrations();
|
||||
if (typeof window.loadIntegrations === 'function') {
|
||||
await window.loadIntegrations();
|
||||
}
|
||||
},
|
||||
typeLabelKey: 'device.icon.entity.mqtt_source',
|
||||
|
||||
@@ -28,8 +28,8 @@ registerIconEntityType('scene_preset', makeSimpleIconAdapter<ScenePreset>({
|
||||
endpointPrefix: '/scene-presets',
|
||||
reload: async () => {
|
||||
scenePresetsCache.invalidate();
|
||||
if (typeof (window as any).loadAutomations === 'function') {
|
||||
await (window as any).loadAutomations();
|
||||
if (typeof window.loadAutomations === 'function') {
|
||||
await window.loadAutomations();
|
||||
}
|
||||
},
|
||||
typeLabelKey: 'device.icon.entity.scene_preset',
|
||||
|
||||
@@ -134,13 +134,13 @@ export function switchSettingsTab(tabId: string): void {
|
||||
window.renderAppearanceTab();
|
||||
}
|
||||
// Lazy-load update settings
|
||||
if (tabId === 'updates' && typeof (window as any).loadUpdateSettings === 'function') {
|
||||
(window as any).initUpdateSettingsPanel();
|
||||
(window as any).loadUpdateSettings();
|
||||
if (tabId === 'updates' && typeof window.loadUpdateSettings === 'function') {
|
||||
window.initUpdateSettingsPanel?.();
|
||||
window.loadUpdateSettings();
|
||||
}
|
||||
// Lazy-render the about panel content
|
||||
if (tabId === 'about' && typeof (window as any).renderAboutPanel === 'function') {
|
||||
(window as any).renderAboutPanel();
|
||||
if (tabId === 'about' && typeof window.renderAboutPanel === 'function') {
|
||||
window.renderAboutPanel();
|
||||
}
|
||||
// Lazy-render the notifications panel (build IconSelects + load prefs)
|
||||
if (tabId === 'notifications') {
|
||||
@@ -530,8 +530,8 @@ export function openSettingsModal(): void {
|
||||
// Refresh the update status so the rail badge ("update available" pill
|
||||
// on the Updates tab) is current when the modal opens — it would
|
||||
// otherwise reflect whatever state the app loaded with.
|
||||
if (typeof (window as any).loadUpdateStatus === 'function') {
|
||||
(window as any).loadUpdateStatus();
|
||||
if (typeof window.loadUpdateStatus === 'function') {
|
||||
window.loadUpdateStatus();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -399,7 +399,7 @@ export function openTestDisplayPicker() {
|
||||
const engineType = currentTestingTemplate?.engine_type || null;
|
||||
const pickerEngineType = _engineHasOwnDisplays(engineType) ? engineType : null;
|
||||
const currentValue = (document.getElementById('test-template-display') as HTMLInputElement).value;
|
||||
openDisplayPicker((window as any).onTestDisplaySelected, currentValue, pickerEngineType);
|
||||
openDisplayPicker(window.onTestDisplaySelected, currentValue, pickerEngineType);
|
||||
}
|
||||
|
||||
async function loadDisplaysForTest() {
|
||||
@@ -436,7 +436,7 @@ async function loadDisplaysForTest() {
|
||||
|
||||
if (selectedIndex !== null && _cachedDisplays) {
|
||||
const display = _cachedDisplays.find(d => d.index === selectedIndex);
|
||||
(window as any).onTestDisplaySelected(selectedIndex, display);
|
||||
window.onTestDisplaySelected(selectedIndex, display);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading displays:', error);
|
||||
|
||||
@@ -80,8 +80,8 @@ import { registerIconEntityType, makeSimpleIconAdapter } from './icon-picker.ts'
|
||||
// ── Icon-picker adapter registrations for streams-tab card types ──
|
||||
|
||||
const _reloadStreams = async () => {
|
||||
if (typeof (window as any).loadPictureSources === 'function') {
|
||||
await (window as any).loadPictureSources();
|
||||
if (typeof window.loadPictureSources === 'function') {
|
||||
await window.loadPictureSources();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -21,8 +21,8 @@ registerIconEntityType('sync_clock', makeSimpleIconAdapter<SyncClock>({
|
||||
endpointPrefix: '/sync-clocks',
|
||||
reload: async () => {
|
||||
syncClocksCache.invalidate();
|
||||
if (typeof (window as any).loadIntegrations === 'function') {
|
||||
await (window as any).loadIntegrations();
|
||||
if (typeof window.loadIntegrations === 'function') {
|
||||
await window.loadIntegrations();
|
||||
}
|
||||
},
|
||||
typeLabelKey: 'device.icon.entity.sync_clock',
|
||||
|
||||
@@ -64,12 +64,12 @@ function _setVersionBadgeUpdate(hasUpdate: boolean): void {
|
||||
}
|
||||
|
||||
export function switchSettingsTabToUpdate(): void {
|
||||
if (typeof (window as any).openSettingsModal === 'function') {
|
||||
(window as any).openSettingsModal();
|
||||
if (typeof window.openSettingsModal === 'function') {
|
||||
window.openSettingsModal();
|
||||
}
|
||||
setTimeout(() => {
|
||||
if (typeof (window as any).switchSettingsTab === 'function') {
|
||||
(window as any).switchSettingsTab('updates');
|
||||
if (typeof window.switchSettingsTab === 'function') {
|
||||
window.switchSettingsTab('updates');
|
||||
}
|
||||
}, 50);
|
||||
}
|
||||
|
||||
@@ -42,8 +42,8 @@ registerIconEntityType('value_source', makeSimpleIconAdapter<any>({
|
||||
endpointPrefix: '/value-sources',
|
||||
reload: async () => {
|
||||
valueSourcesCache.invalidate();
|
||||
if (typeof (window as any).loadIntegrations === 'function') {
|
||||
await (window as any).loadIntegrations();
|
||||
if (typeof window.loadIntegrations === 'function') {
|
||||
await window.loadIntegrations();
|
||||
}
|
||||
},
|
||||
typeLabelKey: 'device.icon.entity.value_source',
|
||||
|
||||
@@ -21,8 +21,8 @@ registerIconEntityType('weather_source', makeSimpleIconAdapter<WeatherSource>({
|
||||
cache: weatherSourcesCache,
|
||||
endpointPrefix: '/weather-sources',
|
||||
reload: async () => {
|
||||
if (typeof (window as any).loadIntegrations === 'function') {
|
||||
await (window as any).loadIntegrations();
|
||||
if (typeof window.loadIntegrations === 'function') {
|
||||
await window.loadIntegrations();
|
||||
}
|
||||
},
|
||||
typeLabelKey: 'device.icon.entity.weather_source',
|
||||
@@ -180,7 +180,7 @@ export async function saveWeatherSource(): Promise<void> {
|
||||
showToast(t(id ? 'weather_source.updated' : 'weather_source.created'), 'success');
|
||||
weatherSourceModal.forceClose();
|
||||
weatherSourcesCache.invalidate();
|
||||
if (typeof (window as any).loadIntegrations === 'function') await (window as any).loadIntegrations();
|
||||
if (typeof window.loadIntegrations === 'function') await window.loadIntegrations();
|
||||
} catch (e: any) {
|
||||
if (e.isAuth) return;
|
||||
weatherSourceModal.showError(e.message);
|
||||
@@ -226,7 +226,7 @@ export async function deleteWeatherSource(sourceId: string): Promise<void> {
|
||||
}
|
||||
showToast(t('weather_source.deleted'), 'success');
|
||||
weatherSourcesCache.invalidate();
|
||||
if (typeof (window as any).loadIntegrations === 'function') await (window as any).loadIntegrations();
|
||||
if (typeof window.loadIntegrations === 'function') await window.loadIntegrations();
|
||||
} catch (e: any) {
|
||||
if (e.isAuth) return;
|
||||
showToast(e.message, 'error');
|
||||
|
||||
+12
-1
@@ -27,7 +27,7 @@ declare global {
|
||||
__t?: (key: string) => string;
|
||||
|
||||
// UI helpers exposed for inline onclick handlers in templates.
|
||||
applyAccentColor?: () => void;
|
||||
applyAccentColor?: (accent?: string, persist?: boolean) => void;
|
||||
hideSetupRequiredModal?: () => void;
|
||||
configureKCRegions?: (sourceId: string) => void;
|
||||
removeZ2MLightMapping?: (btn: HTMLElement) => void;
|
||||
@@ -36,8 +36,19 @@ declare global {
|
||||
// doesn't want a hard module import (to avoid cycles).
|
||||
loadAutomations?: () => Promise<void> | void;
|
||||
loadPictureSources?: () => Promise<void> | void;
|
||||
loadIntegrations?: () => Promise<void> | void;
|
||||
loadUpdateSettings?: () => Promise<void> | void;
|
||||
loadUpdateStatus?: () => Promise<void> | void;
|
||||
showPatternTemplateEditor?: (...args: unknown[]) => unknown;
|
||||
|
||||
// Settings + about panel — bound by app boot and called from
|
||||
// template handlers / nav buttons.
|
||||
initUpdateSettingsPanel?: () => void;
|
||||
onTestDisplaySelected?: (value: string) => void;
|
||||
openSettingsModal?: (...args: unknown[]) => unknown;
|
||||
renderAboutPanel?: () => void;
|
||||
switchSettingsTab?: (tabId: string) => void;
|
||||
|
||||
// Internal helpers attached by features for inline-call reuse.
|
||||
_autoGenerateAutomationName?: () => void;
|
||||
_openKCRegionEditor?: (sourceId: string) => void;
|
||||
|
||||
Reference in New Issue
Block a user