Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 16 additions & 22 deletions frontend/src/features/editor/EditorApp.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { useEffect, useRef, useState } from "react";
import { useEffect, useMemo, useRef, useState } from "react";
import { useGLTF } from "@react-three/drei";
import { useParams } from "react-router-dom";
import { useQuery } from "@tanstack/react-query";
import { posthog } from "@/shared/analytics/posthog";

import useWallSessionQuery from "./utils/WallSessionQuery";
import { usePlacementStore } from "./store";
import type { SessionHoldInstance } from "./store";
import { useHandleLoadSession } from "./utils/HandleLoadSession";

import MainCanvas from "./components/MainCanvas";
Expand All @@ -15,15 +16,6 @@ import FileManager from "./components/FileManager";
import { useTranslation } from "react-i18next";
import Tutorial from "./components/Tutorial";

interface HoldInstance {
id: string;
hold_instance_id?: string;
hold_type?: {
glb_url?: string;
};
[key: string]: unknown;
}

const transformTools = [
{ id: "translate", icon: "open_with", label: "Translate", hint: "Shift + Left click" },
{ id: "rotate", icon: "sync", label: "Rotate", hint: "Left click" },
Expand Down Expand Up @@ -84,23 +76,25 @@ function EditorApp() {
return () => window.removeEventListener("beforeunload", handleBeforeUnload);
}, [hasUnsavedChanges, t]);

const holdModels: Array<Record<string, HoldInstance>> = [];
const holdModelsGLBURL: string[] = [];

useEffect(() => {
const glbUrl = session_data?.related_wall?.glb_url;
setWallModels(glbUrl ? [glbUrl] : []);
}, [session_data?.related_wall?.glb_url]);

if (session_data?.related_holds_collection) {
session_data.holds_collection_instances?.forEach((hold: any) => {
hold.hold_instance_id = hold.id;
holdModels.push(hold);
if (hold.hold_type?.glb_url) {
holdModelsGLBURL.push(hold.hold_type.glb_url);
}
});
}
const { holdModels, holdModelsGLBURL } = useMemo(() => {
const holdModels: SessionHoldInstance[] = [];
const holdModelsGLBURL: string[] = [];
if (session_data?.related_holds_collection) {
session_data.holds_collection_instances?.forEach((hold: SessionHoldInstance) => {
hold.hold_instance_id = hold.id;
holdModels.push(hold);
if (hold.hold_type?.glb_url) {
holdModelsGLBURL.push(hold.hold_type.glb_url);
}
});
Comment on lines +88 to +94

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Mutating the hold object directly inside useMemo is a side effect that should be avoided. Since session_data likely comes from a cache (e.g., TanStack Query), mutating its contents can lead to unexpected behavior in other parts of the application or when the data is re-read from the cache. It is better to create a new object with the added property to maintain immutability.

Suggested change
session_data.holds_collection_instances?.forEach((hold: SessionHoldInstance) => {
hold.hold_instance_id = hold.id;
holdModels.push(hold);
if (hold.hold_type?.glb_url) {
holdModelsGLBURL.push(hold.hold_type.glb_url);
}
});
session_data.holds_collection_instances?.forEach((hold: SessionHoldInstance) => {
const holdWithId = { ...hold, hold_instance_id: hold.id };
holdModels.push(holdWithId);
if (holdWithId.hold_type?.glb_url) {
holdModelsGLBURL.push(holdWithId.hold_type.glb_url);
}
});

}
return { holdModels, holdModelsGLBURL };
}, [session_data?.related_holds_collection, session_data?.holds_collection_instances]);

const { preload } = useGLTF;
useEffect(() => {
Expand Down
29 changes: 15 additions & 14 deletions frontend/src/features/editor/components/AddHoldModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,19 @@ import HandleAddHold from "../utils/HandleAddHold";
import { useEditorAuth } from "../mocks/useEditorAuth";
import PaginationList from "../stubs/PaginationList";
import { useTranslation } from "react-i18next";
import type { HoldModel, SessionData } from "../store";

type HoldModel = {
name: string;
file: string;
hold_type: Record<string, any>;
hold_instance_id: string;
id?: string;
type StockItem = {
id: string | number;
hold_type: { id: string | number; cdn_ref?: string; [key: string]: unknown };
model?: string;
[key: string]: unknown;
};

interface AddHoldModalProps {
isOpen: boolean;
onClose: () => void;
session_data: any;
session_data: SessionData;
onHoldAdded: (hold: HoldModel) => void;
currentHolds: HoldModel[];
}
Expand All @@ -32,7 +32,7 @@ export default function AddHoldModal({
const [search, setSearch] = useState("");
const [isAdding, setIsAdding] = useState(false);
const scrollRef = useRef<HTMLElement>(null);
const [addedHoldIds, setAddedHoldIds] = useState<Set<number>>(new Set());
const [addedHoldIds, setAddedHoldIds] = useState<Set<string | number>>(new Set());
const { user, authenticatedFetch } = useEditorAuth();
const [page, setPage] = useState(1);
const API_URL = import.meta.env.VITE_API_BASE;
Expand Down Expand Up @@ -97,7 +97,7 @@ export default function AddHoldModal({
);

const deduplicatedStockData = Array.isArray(rawStockData)
? rawStockData.reduce((acc: any[], current: any) => {
? rawStockData.reduce((acc: StockItem[], current: StockItem) => {
const exists = acc.find(
(hold) => hold.hold_type.id === current.hold_type.id
);
Expand All @@ -108,24 +108,25 @@ export default function AddHoldModal({

const stockData = Array.isArray(deduplicatedStockData)
? deduplicatedStockData.filter(
(hold: any) =>
(hold: StockItem) =>
!currentHoldTypeIds.has(hold.hold_type.id) &&
!addedHoldIds.has(hold.hold_type.id)
!addedHoldIds.has(hold.hold_type.id as number)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The type cast as number is unnecessary and potentially incorrect. hold.hold_type.id is defined as string | number, and addedHoldIds is a Set<string | number>. Casting to number will cause issues if the ID is actually a string.

Suggested change
!addedHoldIds.has(hold.hold_type.id as number)
!addedHoldIds.has(hold.hold_type.id)

)
: deduplicatedStockData;

const filteredStockData = Array.isArray(stockData)
? stockData.filter((hold: any) => {
? stockData.filter((hold: StockItem) => {
if (!search) return true;
const searchLower = search.toLowerCase();
const manufacturerName = hold.hold_type?.manufacturer_name as string | undefined;
return (
hold.model?.toLowerCase().includes(searchLower) ||
hold.hold_type?.manufacturer_name?.toLowerCase().includes(searchLower)
manufacturerName?.toLowerCase().includes(searchLower)
);
})
: stockData;

const handleAddHoldClick = async (hold: any) => {
const handleAddHoldClick = async (hold: StockItem) => {
setIsAdding(true);
try {
const result = await HandleAddHold(hold, session_data, onHoldAdded);
Expand Down
12 changes: 7 additions & 5 deletions frontend/src/features/editor/components/FileManager.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { useState } from "react";
import { usePlacementStore } from "../store";
import type { SessionData } from "../store";
import { useNavigate } from "react-router-dom";
import { useEditorAuth } from "../mocks/useEditorAuth";
import { useTranslation } from "react-i18next";
import { posthog } from "@/shared/analytics/posthog";

const FileManager = ({ session_data }: { session_data: any }) => {
const FileManager = ({ session_data }: { session_data: SessionData }) => {
const objects = usePlacementStore((s) => s.objects);
const wallColors = usePlacementStore((s) => s.wallColors);
const holdColors = usePlacementStore((s) => s.holdColors);
Expand All @@ -16,15 +17,16 @@ const FileManager = ({ session_data }: { session_data: any }) => {
const navigate = useNavigate();
const { t } = useTranslation();

const cleanObjectsForSave = (objs: typeof objects, data: any) => {
const cleanObjectsForSave = (objs: typeof objects, data: SessionData) => {
return objs.map((obj) => {
const cleanedObj = { ...obj } as any;
if (cleanedObj.type === "wall" && cleanedObj.url?.startsWith("blob:")) {
const cleanedObj: Record<string, unknown> = { ...obj };
const url = cleanedObj.url as string | undefined;
if (cleanedObj.type === "wall" && url?.startsWith("blob:")) {
if (data?.related_wall?.id) {
cleanedObj.wall_id = data.related_wall.id;
}
delete cleanedObj.url;
} else if (cleanedObj.url?.startsWith("blob:")) {
} else if (url?.startsWith("blob:")) {
delete cleanedObj.url;
}
return cleanedObj;
Expand Down
8 changes: 4 additions & 4 deletions frontend/src/features/editor/components/HoldInspector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ function RotationHandle({
const handleY = radius + handleRadius * Math.sin(rotation - Math.PI / 2);
const onPointerDown = (e: React.PointerEvent) => {
e.preventDefault();
window.addEventListener("pointermove", onPointerMove as any);
window.addEventListener("pointerup", onPointerUp as any);
window.addEventListener("pointermove", onPointerMove as EventListener);
window.addEventListener("pointerup", onPointerUp as EventListener);
};
const onPointerMove = (e: PointerEvent) => {
if (!circleRef.current) return;
Expand All @@ -29,8 +29,8 @@ function RotationHandle({
onRotate(angle);
};
const onPointerUp = () => {
window.removeEventListener("pointermove", onPointerMove as any);
window.removeEventListener("pointerup", onPointerUp as any);
window.removeEventListener("pointermove", onPointerMove as EventListener);
window.removeEventListener("pointerup", onPointerUp as EventListener);
};
return (
<div
Expand Down
Loading
Loading