feat: wire App + main with persist + hooks
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
68
src/App.tsx
68
src/App.tsx
@@ -1,49 +1,33 @@
|
|||||||
import { useState } from "react";
|
import { useCallback, useEffect, useState } from "react";
|
||||||
import reactLogo from "./assets/react.svg";
|
import { ControlPanel } from "./components/ControlPanel";
|
||||||
import { invoke } from "@tauri-apps/api/core";
|
import { StatusBar } from "./components/StatusBar";
|
||||||
import "./App.css";
|
import { useLiveOverlayWiring } from "./hooks/useLiveOverlayWiring";
|
||||||
|
import { useClickThroughSync } from "./hooks/useClickThroughSync";
|
||||||
|
import { useOverlayStore } from "./lib/store";
|
||||||
|
import { isOverlayOpen } from "./lib/overlay";
|
||||||
|
|
||||||
function App() {
|
function App(): React.JSX.Element {
|
||||||
const [greetMsg, setGreetMsg] = useState("");
|
useLiveOverlayWiring();
|
||||||
const [name, setName] = useState("");
|
const setIsOpen = useOverlayStore((s) => s.setIsOpen);
|
||||||
|
const [transient, setTransient] = useState<string | null>(null);
|
||||||
|
|
||||||
async function greet() {
|
// Reconcile isOpen with reality on mount (e.g. user closed overlay via taskbar last session).
|
||||||
// Learn more about Tauri commands at https://tauri.app/develop/calling-rust/
|
useEffect(() => {
|
||||||
setGreetMsg(await invoke("greet", { name }));
|
void isOverlayOpen().then(setIsOpen);
|
||||||
}
|
}, [setIsOpen]);
|
||||||
|
|
||||||
|
const handleNoOverlay = useCallback(() => {
|
||||||
|
setTransient("No overlay to toggle");
|
||||||
|
const t = setTimeout(() => setTransient(null), 2500);
|
||||||
|
return () => clearTimeout(t);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useClickThroughSync(handleNoOverlay);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="container">
|
<main className="flex h-screen flex-col bg-[var(--color-bg)]">
|
||||||
<h1>Welcome to Tauri + React</h1>
|
<ControlPanel />
|
||||||
|
<StatusBar transient={transient} />
|
||||||
<div className="row">
|
|
||||||
<a href="https://vite.dev" target="_blank">
|
|
||||||
<img src="/vite.svg" className="logo vite" alt="Vite logo" />
|
|
||||||
</a>
|
|
||||||
<a href="https://tauri.app" target="_blank">
|
|
||||||
<img src="/tauri.svg" className="logo tauri" alt="Tauri logo" />
|
|
||||||
</a>
|
|
||||||
<a href="https://react.dev" target="_blank">
|
|
||||||
<img src={reactLogo} className="logo react" alt="React logo" />
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<p>Click on the Tauri, Vite, and React logos to learn more.</p>
|
|
||||||
|
|
||||||
<form
|
|
||||||
className="row"
|
|
||||||
onSubmit={(e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
greet();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
id="greet-input"
|
|
||||||
onChange={(e) => setName(e.currentTarget.value)}
|
|
||||||
placeholder="Enter a name..."
|
|
||||||
/>
|
|
||||||
<button type="submit">Greet</button>
|
|
||||||
</form>
|
|
||||||
<p>{greetMsg}</p>
|
|
||||||
</main>
|
</main>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
32
src/lib/hotkey.ts
Normal file
32
src/lib/hotkey.ts
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import { invoke } from "@tauri-apps/api/core";
|
||||||
|
import { useOverlayStore } from "./store";
|
||||||
|
|
||||||
|
type HotkeyStatus =
|
||||||
|
| { kind: "pending" }
|
||||||
|
| { kind: "ok" }
|
||||||
|
| { kind: "failed"; error: string };
|
||||||
|
|
||||||
|
/** Polls the backend until hotkey registration is decided, then surfaces any error
|
||||||
|
* via the store. Bounded retry: setup-time decision is essentially synchronous
|
||||||
|
* on the Rust side, so a missed first call is at worst a few-ms wait. */
|
||||||
|
export async function fetchHotkeyStatus(): Promise<void> {
|
||||||
|
const maxAttempts = 20;
|
||||||
|
for (let i = 0; i < maxAttempts; i++) {
|
||||||
|
let status: HotkeyStatus;
|
||||||
|
try {
|
||||||
|
status = await invoke<HotkeyStatus>("get_hotkey_status");
|
||||||
|
} catch (err) {
|
||||||
|
console.warn("get_hotkey_status invocation failed", err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (status.kind === "pending") {
|
||||||
|
await new Promise((r) => setTimeout(r, 50));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
useOverlayStore.getState().setHotkeyError(
|
||||||
|
status.kind === "failed" ? status.error : null,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.warn("hotkey status remained pending after retries");
|
||||||
|
}
|
||||||
30
src/main.tsx
30
src/main.tsx
@@ -1,9 +1,29 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import ReactDOM from "react-dom/client";
|
import ReactDOM from "react-dom/client";
|
||||||
import App from "./App";
|
import App from "./App";
|
||||||
|
import "./index.css";
|
||||||
|
import { hydrateFromDisk, startPersistSubscription, flushPendingPersistSync } from "./lib/persist";
|
||||||
|
import { fetchHotkeyStatus } from "./lib/hotkey";
|
||||||
|
|
||||||
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
|
async function bootstrap(): Promise<void> {
|
||||||
<React.StrictMode>
|
await hydrateFromDisk();
|
||||||
<App />
|
startPersistSubscription();
|
||||||
</React.StrictMode>,
|
// Pull the hotkey registration result. Pull-style avoids the
|
||||||
);
|
// listener-not-yet-registered race that an event-based approach would have.
|
||||||
|
void fetchHotkeyStatus();
|
||||||
|
|
||||||
|
// beforeunload handlers must be synchronous; we use a sync flush helper
|
||||||
|
// that issues the write. The async save will race with window destruction
|
||||||
|
// — whichever wins is fine, the write either lands or we retry next session.
|
||||||
|
window.addEventListener("beforeunload", () => {
|
||||||
|
flushPendingPersistSync();
|
||||||
|
});
|
||||||
|
|
||||||
|
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
|
||||||
|
<React.StrictMode>
|
||||||
|
<App />
|
||||||
|
</React.StrictMode>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void bootstrap();
|
||||||
|
|||||||
Reference in New Issue
Block a user