feat: live overlay wiring and backend click-through sync hooks

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Michael Chihlas
2026-05-08 18:53:18 -04:00
parent fdeb6e8a19
commit 8359e4df45
2 changed files with 82 additions and 0 deletions

View File

@@ -0,0 +1,48 @@
import { useEffect } from "react";
import { listen, type UnlistenFn } from "@tauri-apps/api/event";
import { useOverlayStore } from "../lib/store";
import { applyOpacity } from "../lib/overlay";
import {
EVT_CLICK_THROUGH_NO_OVERLAY,
EVT_CLICK_THROUGH_TOGGLED,
type ClickThroughToggledPayload,
} from "../types/overlay";
/** Subscribes to backend events about click-through state and toast about no-overlay attempts. */
export function useClickThroughSync(
onNoOverlay: () => void
): void {
useEffect(() => {
let unlistenToggled: UnlistenFn | undefined;
let unlistenNoOverlay: UnlistenFn | undefined;
let cancelled = false;
(async () => {
unlistenToggled = await listen<ClickThroughToggledPayload>(
EVT_CLICK_THROUGH_TOGGLED,
(e) => {
if (cancelled) return;
useOverlayStore.getState()._setClickThroughFromBackend(
e.payload.clickThrough
);
// Restore the user's target opacity. Rust performed the dip-half of
// the pulse and emitted this event after sleeping ~180ms; we own
// the restore. Reading from the live store guarantees we restore
// to whatever the slider says *now*, not whatever it said when the
// hotkey was pressed.
void applyOpacity(useOverlayStore.getState().opacity);
}
);
unlistenNoOverlay = await listen(EVT_CLICK_THROUGH_NO_OVERLAY, () => {
if (cancelled) return;
onNoOverlay();
});
})().catch((err) => console.warn("event listen failed", err));
return () => {
cancelled = true;
unlistenToggled?.();
unlistenNoOverlay?.();
};
}, [onNoOverlay]);
}

View File

@@ -0,0 +1,34 @@
import { useEffect, useRef } from "react";
import { useOverlayStore } from "../lib/store";
import {
applyAlwaysOnTop,
applyClickThrough,
applyOpacity,
} from "../lib/overlay";
/** Pushes Zustand changes to the overlay window in real time. No-op when overlay is closed. */
export function useLiveOverlayWiring(): void {
const opacity = useOverlayStore((s) => s.opacity);
const alwaysOnTop = useOverlayStore((s) => s.alwaysOnTop);
const clickThrough = useOverlayStore((s) => s.clickThrough);
const isOpen = useOverlayStore((s) => s.isOpen);
const lastAppliedClickThrough = useRef<boolean | null>(null);
useEffect(() => {
if (!isOpen) return;
void applyOpacity(opacity);
}, [opacity, isOpen]);
useEffect(() => {
if (!isOpen) return;
void applyAlwaysOnTop(alwaysOnTop);
}, [alwaysOnTop, isOpen]);
useEffect(() => {
if (!isOpen) return;
if (lastAppliedClickThrough.current === clickThrough) return;
lastAppliedClickThrough.current = clickThrough;
void applyClickThrough(clickThrough);
}, [clickThrough, isOpen]);
}