feat: initialize TJWater WebGIS frontend

This commit is contained in:
2026-07-21 18:31:08 +08:00
commit ad5e5d3133
191 changed files with 37963 additions and 0 deletions
+72
View File
@@ -0,0 +1,72 @@
import type { RiveParameters } from "@rive-app/react-webgl2";
import { useRive, useStateMachineInput } from "@rive-app/react-webgl2";
import { memo, useEffect, useState } from "react";
import { cn } from "@/lib/utils";
const RIVE_SOURCE = "https://ejiidnob33g9ap1r.public.blob.vercel-storage.com/obsidian-2.0.riv";
const STATE_MACHINE = "default";
function useDeferredRiveInit() {
const [ready, setReady] = useState(false);
useEffect(() => {
const id = requestAnimationFrame(() => setReady(true));
return () => cancelAnimationFrame(id);
}, []);
return ready;
}
export type PersonaState = "idle" | "listening" | "thinking" | "speaking" | "asleep";
export type PersonaProps = {
className?: string;
onLoadError?: RiveParameters["onLoadError"];
state?: PersonaState;
};
export const Persona = memo(function Persona({
className,
onLoadError,
state = "idle"
}: PersonaProps) {
const ready = useDeferredRiveInit();
const { rive, RiveComponent } = useRive(
ready
? {
autoplay: true,
onLoadError,
src: RIVE_SOURCE,
stateMachines: STATE_MACHINE
}
: null
);
const listeningInput = useStateMachineInput(rive, STATE_MACHINE, "listening");
const thinkingInput = useStateMachineInput(rive, STATE_MACHINE, "thinking");
const speakingInput = useStateMachineInput(rive, STATE_MACHINE, "speaking");
const asleepInput = useStateMachineInput(rive, STATE_MACHINE, "asleep");
useEffect(() => {
if (listeningInput) {
listeningInput.value = state === "listening";
}
if (thinkingInput) {
thinkingInput.value = state === "thinking";
}
if (speakingInput) {
speakingInput.value = state === "speaking";
}
if (asleepInput) {
asleepInput.value = state === "asleep";
}
}, [state, listeningInput, thinkingInput, speakingInput, asleepInput]);
return (
<RiveComponent
className={cn("size-16 shrink-0", className)}
style={{ filter: "brightness(0.45) contrast(1.35)" }}
/>
);
});