132 lines
4.4 KiB
TypeScript
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>
|
|
);
|
|
}
|