refactor: clean architecture and remove unused code

This commit is contained in:
Tom Boullay
2026-04-30 13:33:28 +02:00
parent b1187b68ae
commit cfb1eaf39a
30 changed files with 303 additions and 696 deletions
+10 -3
View File
@@ -15,6 +15,13 @@ export function useModelSelection(
): UseModelSelectionResult {
const [isOpen, setIsOpen] = useState(false);
const [selectedIndex, setSelectedIndex] = useState(0);
const firstModel = models[0];
if (!firstModel) {
throw new Error("useModelSelection requires at least one model");
}
const selectedModel = models[selectedIndex] ?? firstModel;
const close = useCallback(() => setIsOpen(false), []);
const open = useCallback(() => setIsOpen(true), []);
@@ -42,7 +49,7 @@ export function useModelSelection(
}
if (key === "e" || key === "enter") {
onSelect(models[selectedIndex]);
onSelect(selectedModel);
close();
event.preventDefault();
event.stopPropagation();
@@ -60,12 +67,12 @@ export function useModelSelection(
return () => {
window.removeEventListener("keydown", handleKeyDown, { capture: true });
};
}, [close, isOpen, models, onSelect, selectedIndex]);
}, [close, isOpen, models, onSelect, selectedModel]);
return {
isOpen,
selectedIndex,
selectedModel: models[selectedIndex],
selectedModel,
open,
close,
};
@@ -10,6 +10,7 @@ import {
} from "@/data/handTrackingConfig";
import type {
HandTrackingFrameMessage,
HandTrackingHand,
HandTrackingServerMessage,
HandTrackingSnapshot,
} from "@/types/handTracking/handTracking";
@@ -31,6 +32,58 @@ function getBase64Payload(dataUrl: string): string {
return dataUrl.slice(dataUrl.indexOf(",") + 1);
}
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null;
}
function isFiniteNumber(value: unknown): value is number {
return typeof value === "number" && Number.isFinite(value);
}
function isHandTrackingLandmark(value: unknown): boolean {
return (
isRecord(value) &&
isFiniteNumber(value.x) &&
isFiniteNumber(value.y) &&
isFiniteNumber(value.z)
);
}
function isHandTrackingHand(value: unknown): value is HandTrackingHand {
return (
isRecord(value) &&
isFiniteNumber(value.x) &&
isFiniteNumber(value.y) &&
isFiniteNumber(value.z) &&
Array.isArray(value.landmarks) &&
value.landmarks.every(isHandTrackingLandmark) &&
typeof value.handedness === "string" &&
typeof value.isFist === "boolean" &&
isFiniteNumber(value.score)
);
}
function isHandTrackingServerMessage(
value: unknown,
): value is HandTrackingServerMessage {
if (!isRecord(value) || !isFiniteNumber(value.timestamp)) return false;
if (value.type === "hands") {
return Array.isArray(value.hands) && value.hands.every(isHandTrackingHand);
}
if (value.type === "status") {
return typeof value.status === "string";
}
return (
value.type === "error" &&
Array.isArray(value.hands) &&
value.hands.every(isHandTrackingHand) &&
typeof value.message === "string"
);
}
function getCameraStreamWithTimeout(
constraints: MediaStreamConstraints,
): Promise<MediaStream> {
@@ -106,6 +159,16 @@ export function useRemoteHandTracking({
clearResponseTimeout();
};
const markInvalidResponse = (): void => {
setSnapshot((current) => ({
...current,
hands: [],
status: "error",
usageStatus: "inactive",
error: "Invalid hand tracking response",
}));
};
const sendFrame = (): void => {
const ws = wsRef.current;
const video = videoRef.current;
@@ -201,7 +264,23 @@ export function useRemoteHandTracking({
};
ws.onmessage = (event) => {
markResponseReceived();
const data = JSON.parse(event.data) as HandTrackingServerMessage;
if (typeof event.data !== "string") {
markInvalidResponse();
return;
}
let data: unknown;
try {
data = JSON.parse(event.data);
} catch {
markInvalidResponse();
return;
}
if (!isHandTrackingServerMessage(data)) {
markInvalidResponse();
return;
}
if (data.type === "hands") {
setSnapshot((current) => ({
+6
View File
@@ -0,0 +1,6 @@
import { useMemo } from "react";
import type * as THREE from "three";
export function useClonedObject<T extends THREE.Object3D>(object: T): T {
return useMemo(() => object.clone(true) as T, [object]);
}