Phase 2.2: performance optimizations, debug tools, app close fix
This commit is contained in:
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user