Phase 2.2: performance optimizations, debug tools, app close fix

This commit is contained in:
2026-01-31 20:31:53 +02:00
parent fb2b55caff
commit 26f8067c94
18 changed files with 1006 additions and 167 deletions

View File

@@ -28,7 +28,8 @@ import Button from '@/components/ui/Button.tsx';
import NumberInput from '@/components/ui/NumberInput.tsx';
import ConfirmDialog from '@/components/ui/ConfirmDialog.tsx';
import { RegionWizard } from '@/components/RegionWizard.tsx';
import { isDesktop } from '@/lib/desktop.ts';
import { isDesktop, getDesktopApi } from '@/lib/desktop.ts';
import type { RegionInfo, CacheStats } from '@/services/api.ts';
/**
* Restore a sites snapshot: replace all sites in IndexedDB + Zustand.
@@ -121,8 +122,18 @@ export default function App() {
// Region wizard for first-run (desktop mode only)
const [showWizard, setShowWizard] = useState(false);
const [cachedRegions, setCachedRegions] = useState<RegionInfo[]>([]);
const [cacheStats, setCacheStats] = useState<CacheStats | null>(null);
const refreshCacheStatus = useCallback(() => {
api.getRegions().then(setCachedRegions).catch(() => {});
api.getCacheStats().then(setCacheStats).catch(() => {});
}, []);
useEffect(() => {
// Load cache status on mount
refreshCacheStatus();
if (!isDesktop()) return;
const skipped = localStorage.getItem('rfcp_region_wizard_skipped');
if (skipped) return;
@@ -137,7 +148,7 @@ export default function App() {
.catch(() => {
// Backend not ready yet, skip wizard
});
}, []);
}, [refreshCacheStatus]);
// Resizable sidebar
const PANEL_MIN = 300;
@@ -1051,6 +1062,75 @@ export default function App() {
</div>
</div>
{/* Data Cache Status */}
<div className="bg-white dark:bg-dark-surface border border-gray-200 dark:border-dark-border rounded-lg shadow-sm p-4 space-y-2">
<h3 className="text-sm font-semibold text-gray-800 dark:text-dark-text">
Data Cache
</h3>
{cachedRegions.length > 0 ? (
<>
<div className="space-y-1">
{cachedRegions.filter((r) => r.downloaded || r.download_progress > 0).length > 0 ? (
cachedRegions
.filter((r) => r.downloaded || r.download_progress > 0)
.map((r) => (
<div key={r.id} className="flex items-center gap-2 text-xs">
<span
className={`w-1.5 h-1.5 rounded-full flex-shrink-0 ${
r.downloaded ? 'bg-emerald-500' : 'bg-amber-500'
}`}
/>
<span className="text-gray-700 dark:text-dark-text truncate">{r.name}</span>
{!r.downloaded && (
<span className="text-gray-400 dark:text-dark-muted ml-auto">
{Math.round(r.download_progress)}%
</span>
)}
</div>
))
) : (
<p className="text-xs text-gray-400 dark:text-dark-muted">No regions cached</p>
)}
</div>
{cacheStats && (
<p className="text-[11px] text-gray-400 dark:text-dark-muted">
{cacheStats.terrain_tiles} terrain tiles ({cacheStats.terrain_mb} MB)
</p>
)}
</>
) : (
<p className="text-xs text-gray-400 dark:text-dark-muted">Loading...</p>
)}
<div className="flex gap-2 pt-1">
<button
onClick={() => setShowWizard(true)}
className="text-xs px-2 py-1 bg-slate-100 dark:bg-dark-border text-gray-600 dark:text-dark-text rounded hover:bg-slate-200 dark:hover:bg-dark-muted transition-colors"
>
Download Regions
</button>
{isDesktop() && (
<button
onClick={async () => {
const desktop = getDesktopApi();
if (!desktop) return;
const result = await desktop.importRegionData();
if (result.success) {
addToast(result.message, 'success');
refreshCacheStatus();
} else {
if (result.message !== 'Cancelled') {
addToast(result.message, 'error');
}
}
}}
className="text-xs px-2 py-1 bg-slate-100 dark:bg-dark-border text-gray-600 dark:text-dark-text rounded hover:bg-slate-200 dark:hover:bg-dark-muted transition-colors"
>
Import Data
</button>
)}
</div>
</div>
{/* Coverage error */}
{coverageError && (
<div className="bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-lg p-3">
@@ -1109,7 +1189,7 @@ export default function App() {
{/* First-run region download wizard (desktop only) */}
{showWizard && (
<RegionWizard onComplete={() => setShowWizard(false)} />
<RegionWizard onComplete={() => { setShowWizard(false); refreshCacheStatus(); }} />
)}
</div>
);

View File

@@ -14,6 +14,12 @@ interface RFCPDesktop {
setSetting: (key: string, value: unknown) => Promise<void>;
openExternal: (url: string) => Promise<void>;
openPath: (path: string) => Promise<void>;
importRegionData: () => Promise<{
success: boolean;
terrainCount?: number;
osmCount?: number;
message: string;
}>;
platform: string;
isDesktop: boolean;
isMac: boolean;