Files
TougliGui/src/App.tsx
2026-04-25 10:28:35 +02:00

132 lines
4.4 KiB
TypeScript

import { useEffect, useState } from "react";
import { invoke } from "@tauri-apps/api/core";
import { getCurrentWindow, LogicalSize } from "@tauri-apps/api/window";
import { WebviewWindow } from "@tauri-apps/api/webviewWindow";
import { useStore } from "./store";
import TitleBar from "./components/TitleBar";
import ResizeHandles from "./components/ResizeHandles";
import HomeView from "./components/HomeView";
import GuideView from "./components/GuideView";
import SettingsPanel from "./components/SettingsPanel";
import ProfileModal from "./components/ProfileModal";
import SyncOverlay from "./components/SyncOverlay";
export default function App() {
const { loadProfiles, loadGuides, openGuide, setResourcesPanelCollapsed, view, syncing, syncGuides } = useStore();
const [showSettings, setShowSettings] = useState(false);
const [needsProfile, setNeedsProfile] = useState(false);
const [needsSync, setNeedsSync] = useState(false);
async function runPhase2() {
const has = await invoke<boolean>("has_guides");
if (!has) {
setNeedsSync(true);
} else {
await loadGuides();
const lastGuide = await invoke<string | null>("get_setting", { key: "active_guide" });
if (lastGuide) {
try {
await openGuide(lastGuide);
setResourcesPanelCollapsed(true);
} catch { /* guide may no longer exist */ }
}
}
}
useEffect(() => {
async function init() {
const [savedW, savedH] = await Promise.all([
invoke<string | null>("get_setting", { key: "window_width" }),
invoke<string | null>("get_setting", { key: "window_height" }),
]);
if (savedW && savedH) {
await getCurrentWindow().setSize(new LogicalSize(parseInt(savedW), parseInt(savedH)));
}
await loadProfiles();
const profiles = useStore.getState().profiles;
if (profiles.length === 0) {
setNeedsProfile(true);
return;
}
await runPhase2();
}
init();
// Persist window size on resize (debounced)
const win = getCurrentWindow();
let debounce: ReturnType<typeof setTimeout> | null = null;
const unlisten = win.onResized(async () => {
if (debounce !== null) clearTimeout(debounce);
debounce = setTimeout(async () => {
const size = await win.innerSize();
const factor = await win.scaleFactor();
const w = Math.round(size.width / factor);
const h = Math.round(size.height / factor);
await invoke("set_setting", { key: "window_width", value: w.toString() });
await invoke("set_setting", { key: "window_height", value: h.toString() });
}, 500);
});
const unlistenFocus = win.listen("tauri://focus", async () => {
const viewer = await WebviewWindow.getByLabel("image-viewer");
if (viewer) {
const isMin = await viewer.isMinimized();
if (isMin) await viewer.unminimize();
}
});
const unlistenBlur = win.listen("tauri://blur", async () => {
const isMin = await win.isMinimized();
if (isMin) {
const viewer = await WebviewWindow.getByLabel("image-viewer");
if (viewer) await viewer.minimize();
}
});
return () => {
unlisten.then(f => f());
unlistenFocus.then(f => f());
unlistenBlur.then(f => f());
};
}, []);
async function handleProfileCreated() {
setNeedsProfile(false);
await runPhase2();
}
async function handleInitialSync() {
if (!needsSync) return;
setNeedsSync(false);
await syncGuides();
}
return (
<div className="app-shell">
<ResizeHandles />
<TitleBar onOpenSettings={() => setShowSettings(s => !s)} />
<div className="app-body">
<main className="app-main">
{view === "home" ? <HomeView needsSync={needsSync} onSync={handleInitialSync} /> : <GuideView />}
</main>
</div>
{showSettings && <SettingsPanel onClose={() => setShowSettings(false)} />}
{syncing && !showSettings && <SyncOverlay />}
{needsProfile && <ProfileModal blocking onClose={handleProfileCreated} />}
<style>{`
.app-shell {
display: flex; flex-direction: column; height: 100vh;
background: #0d1117; overflow: hidden;
}
.app-body { display: flex; flex: 1; overflow: hidden; }
.app-main { flex: 1; overflow: hidden; display: flex; flex-direction: column; min-height: 0; }
`}</style>
</div>
);
}