Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 2 additions & 11 deletions src/app/country-intel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import type {
} from '@/components/CountryBriefPanel';
import { CountryDeepDivePanel } from '@/components/CountryDeepDivePanel';
import { reverseGeocode } from '@/utils/reverse-geocode';
import { showToast } from '@/utils/toast';
import {
getCountryAtCoordinates,
getCountryCentroid,
Expand Down Expand Up @@ -862,7 +863,7 @@ export class CountryIntelManager implements AppModule {

openCountryStory(code: string, name: string): void {
if (!dataFreshness.hasSufficientData() || this.ctx.latestClusters.length === 0) {
this.showToast('Data still loading — try again in a moment');
showToast('Data still loading — try again in a moment');
return;
}
const posturePanel = this.ctx.panels['strategic-posture'] as StrategicPosturePanel | undefined;
Expand All @@ -879,16 +880,6 @@ export class CountryIntelManager implements AppModule {
openStoryModal(data);
}

showToast(msg: string): void {
document.querySelector('.toast-notification')?.remove();
const el = document.createElement('div');
el.className = 'toast-notification';
el.textContent = msg;
document.body.appendChild(el);
requestAnimationFrame(() => el.classList.add('visible'));
setTimeout(() => { el.classList.remove('visible'); setTimeout(() => el.remove(), 300); }, 3000);
}

private getCountryStrikes(code: string, hasGeoShape: boolean): typeof this.ctx.intelligenceCache.iranEvents & object {
if (!this.ctx.intelligenceCache.iranEvents) return [];
const seen = new Set<string>();
Expand Down
10 changes: 0 additions & 10 deletions src/app/event-handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1082,16 +1082,6 @@ export class EventHandlerManager implements AppModule {
}
}

showToast(msg: string): void {
document.querySelector('.toast-notification')?.remove();
const el = document.createElement('div');
el.className = 'toast-notification';
el.textContent = msg;
document.body.appendChild(el);
requestAnimationFrame(() => el.classList.add('visible'));
setTimeout(() => { el.classList.remove('visible'); setTimeout(() => el.remove(), 300); }, 3000);
}

shouldShowIntelligenceNotifications(): boolean {
return !this.ctx.isMobile && !!this.ctx.findingsBadge?.isPopupEnabled();
}
Expand Down
2 changes: 2 additions & 0 deletions src/components/UnifiedSettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { SITE_VARIANT } from '@/config/variant';
import { t } from '@/services/i18n';
import type { MapProvider } from '@/config/basemap';
import { escapeHtml } from '@/utils/sanitize';
import { showToast } from '@/utils/toast';
import type { PanelConfig } from '@/types';
import { renderPreferences } from '@/services/preferences-content';

Expand Down Expand Up @@ -200,6 +201,7 @@ export class UnifiedSettings {
const prefs = renderPreferences({
isDesktopApp: this.config.isDesktopApp,
onMapProviderChange: this.config.onMapProviderChange,
onSettingSaved: () => showToast(t('modals.settingsWindow.saved')),
});

this.overlay.innerHTML = `
Expand Down
36 changes: 13 additions & 23 deletions src/services/preferences-content.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
export interface PreferencesHost {
isDesktopApp: boolean;
onMapProviderChange?: (provider: MapProvider) => void;
/** Called when a preference is saved (for visual feedback e.g. toast). Not called for import/export. */
onSettingSaved?: () => void;
}

export interface PreferencesResult {
Expand Down Expand Up @@ -279,44 +281,29 @@

if (target.id === 'us-stream-quality') {
setStreamQuality(target.value as StreamQuality);
return;
}
if (target.id === 'us-globe-visual-preset') {
} else if (target.id === 'us-globe-visual-preset') {
setGlobeVisualPreset(target.value as GlobeVisualPreset);
return;
}
if (target.id === 'us-theme') {
} else if (target.id === 'us-theme') {
setThemePreference(target.value as ThemePreference);
return;
}
if (target.id === 'us-font-family') {
} else if (target.id === 'us-font-family') {
setFontFamily(target.value as FontFamily);
return;
} else if (target.id === 'us-map-provider') {
}
if (target.id === 'us-map-provider') {
const provider = target.value as MapProvider;
setMapProvider(provider);
renderMapThemeDropdown(container, provider);
host.onMapProviderChange?.(provider);
window.dispatchEvent(new CustomEvent('map-theme-changed'));
return;
}
if (target.id === 'us-map-theme') {
} else if (target.id === 'us-map-theme') {

Check failure on line 297 in src/services/preferences-content.ts

View workflow job for this annotation

GitHub Actions / typecheck

Property assignment expected.

Check failure on line 297 in src/services/preferences-content.ts

View workflow job for this annotation

GitHub Actions / typecheck

',' expected.

Check failure on line 297 in src/services/preferences-content.ts

View workflow job for this annotation

GitHub Actions / typecheck

',' expected.

Check failure on line 297 in src/services/preferences-content.ts

View workflow job for this annotation

GitHub Actions / typecheck

':' expected.

Check failure on line 297 in src/services/preferences-content.ts

View workflow job for this annotation

GitHub Actions / typecheck

',' expected.
const provider = getMapProvider();
setMapTheme(provider, target.value);
window.dispatchEvent(new CustomEvent('map-theme-changed'));
return;
}
if (target.id === 'us-live-streams-always-on') {
} else if (target.id === 'us-live-streams-always-on') {

Check failure on line 301 in src/services/preferences-content.ts

View workflow job for this annotation

GitHub Actions / typecheck

Declaration or statement expected.
setLiveStreamsAlwaysOn(target.checked);
return;
}
if (target.id === 'us-language') {
} else if (target.id === 'us-language') {
trackLanguageChange(target.value);
void changeLanguage(target.value);
return;
}
if (target.id === 'us-cloud') {
} else if (target.id === 'us-cloud') {
setAiFlowSetting('cloudLlm', target.checked);
updateAiStatus(container);
} else if (target.id === 'us-browser') {
Expand All @@ -330,8 +317,11 @@
setAiFlowSetting('headlineMemory', target.checked);
} else if (target.id === 'us-badge-anim') {
setAiFlowSetting('badgeAnimation', target.checked);
} else {
return; // not a preference we persist (e.g. import input already returned above)
}
host.onSettingSaved?.();
}, { signal });

Check failure on line 324 in src/services/preferences-content.ts

View workflow job for this annotation

GitHub Actions / typecheck

Declaration or statement expected.

Check failure on line 324 in src/services/preferences-content.ts

View workflow job for this annotation

GitHub Actions / typecheck

Declaration or statement expected.

container.addEventListener('click', (e) => {
const target = e.target as HTMLElement;
Expand All @@ -353,7 +343,7 @@
if (!host.isDesktopApp) updateAiStatus(container);

return () => ac.abort();
},

Check failure on line 346 in src/services/preferences-content.ts

View workflow job for this annotation

GitHub Actions / typecheck

Declaration or statement expected.

Check failure on line 346 in src/services/preferences-content.ts

View workflow job for this annotation

GitHub Actions / typecheck

Declaration or statement expected.
};
}

Expand Down
17 changes: 17 additions & 0 deletions src/utils/toast.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/**
* Global toast notification (body-level). Use for short-lived feedback e.g. "Settings saved".
* For in-panel toasts with custom styling (e.g. import/export), use component-specific logic.
*/
export function showToast(msg: string): void {
document.querySelector('.toast-notification')?.remove();
const el = document.createElement('div');
el.className = 'toast-notification';
el.setAttribute('role', 'status');
el.textContent = msg;
document.body.appendChild(el);
requestAnimationFrame(() => el.classList.add('visible'));
setTimeout(() => {
el.classList.remove('visible');
setTimeout(() => el.remove(), 300);
}, 3000);
}
Loading