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:
48
src/hooks/useClickThroughSync.ts
Normal file
48
src/hooks/useClickThroughSync.ts
Normal 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]);
|
||||||
|
}
|
||||||
34
src/hooks/useLiveOverlayWiring.ts
Normal file
34
src/hooks/useLiveOverlayWiring.ts
Normal 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]);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user