Dwinzo_dev/app/src/components/ui/Tools.tsx

417 lines
12 KiB
TypeScript
Raw Normal View History

2025-03-25 06:17:41 +00:00
import React, { useEffect, useRef, useState } from "react";
import {
AsileIcon,
CommentIcon,
CursorIcon,
2025-03-29 07:27:16 +00:00
DeleteIcon,
2025-03-25 06:17:41 +00:00
FloorIcon,
FreeMoveIcon,
2025-03-29 07:27:16 +00:00
MeasureToolIcon,
2025-03-25 06:17:41 +00:00
PenIcon,
PlayIcon,
SaveTemplateIcon,
WallIcon,
ZoneIcon,
} from "../icons/ExportToolsIcons";
import { ArrowIcon, TickIcon } from "../icons/ExportCommonIcons";
import useModuleStore, { useThreeDStore } from "../../store/useModuleStore";
import { handleSaveTemplate } from "../../modules/visualization/functions/handleSaveTemplate";
2025-03-25 06:17:41 +00:00
import { usePlayButtonStore } from "../../store/usePlayButtonStore";
import useTemplateStore from "../../store/useTemplateStore";
import { useSelectedZoneStore } from "../../store/visualization/useZoneStore";
2025-03-26 06:00:17 +00:00
import {
useActiveTool,
2025-03-26 06:00:17 +00:00
useAddAction,
useDeleteTool,
useDeletePointOrLine,
useRefTextUpdate,
2025-03-26 06:00:17 +00:00
useSelectedWallItem,
useSocketStore,
2025-03-26 06:00:17 +00:00
useToggleView,
useToolMode,
useActiveSubTool,
2025-05-13 11:20:50 +00:00
useShortcutStore,
} from "../../store/builder/store";
2025-03-29 07:27:16 +00:00
import useToggleStore from "../../store/useUIToggleStore";
2025-04-02 13:42:14 +00:00
import {
use3DWidget,
useFloatingWidget,
} from "../../store/visualization/useDroppedObjectsStore";
2025-03-25 06:17:41 +00:00
// Utility component
const ToolButton = ({
toolKey,
toolId,
icon: Icon,
active,
onClick,
tooltip,
}: any) => (
<button
key={toolKey} // used in rendering list
id={toolId}
className={`tool-button ${active ? "active" : ""}`}
onClick={onClick}
>
<div className="tooltip">{tooltip}</div>
<div className="tool" id={toolId}>
<Icon isActive={active} />
</div>
</button>
);
2025-03-25 06:17:41 +00:00
const Tools: React.FC = () => {
const { activeModule } = useModuleStore();
const { toggleThreeD, setToggleThreeD } = useThreeDStore();
const { isPlaying, setIsPlaying } = usePlayButtonStore();
const { showShortcuts } = useShortcutStore();
const {
activeTool,
setActiveTool,
setToolMode,
setAddAction,
setDeleteTool,
setDeletePointOrLine,
} = useStoreHooks();
const { setActiveSubTool, activeSubTool } = useActiveSubTool();
const { setSelectedWallItem } = useSelectedWallItem();
const { setRefTextUpdate } = useRefTextUpdate();
const { setToggleUI } = useToggleStore();
const { setToggleView, toggleView } = useToggleView();
2025-03-25 06:17:41 +00:00
const { addTemplate, templates } = useTemplateStore();
2025-03-25 06:17:41 +00:00
const { selectedZone } = useSelectedZoneStore();
2025-04-02 13:42:14 +00:00
const { floatingWidget } = useFloatingWidget();
const { widgets3D } = use3DWidget();
const { visualizationSocket } = useSocketStore();
2025-03-25 06:17:41 +00:00
const dropdownRef = useRef<HTMLButtonElement>(null);
const [openDrop, setOpenDrop] = useState(false);
// 1. Set UI toggles on initial render
2025-03-29 07:27:16 +00:00
useEffect(() => {
setToggleUI(
localStorage.getItem("navBarUiLeft") !== "false",
localStorage.getItem("navBarUiRight") !== "false"
);
// eslint-disable-next-line react-hooks/exhaustive-deps
2025-03-29 07:27:16 +00:00
}, []);
// 2. Update tool based on subtool and module
2025-03-25 06:17:41 +00:00
useEffect(() => {
setActiveTool(activeSubTool);
setActiveSubTool(activeSubTool);
// eslint-disable-next-line react-hooks/exhaustive-deps
2025-03-25 06:17:41 +00:00
}, [activeModule]);
// 3. Update tools behavior based on selected tool and view mode
useEffect(() => {
resetTools();
updateToolBehavior(activeTool, toggleView);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [activeTool, toggleView]);
// 4. Dropdown auto-close
2025-03-25 06:17:41 +00:00
useEffect(() => {
const handleClickOutside = (e: MouseEvent) => {
2025-03-25 06:17:41 +00:00
if (
dropdownRef.current &&
!dropdownRef.current.contains(e.target as Node)
2025-03-25 06:17:41 +00:00
) {
setOpenDrop(false);
2025-03-25 06:17:41 +00:00
}
};
document.addEventListener("mousedown", handleClickOutside);
return () => document.removeEventListener("mousedown", handleClickOutside);
2025-03-25 06:17:41 +00:00
}, []);
const resetTools = () => {
setToolMode(null);
setDeleteTool(false);
setAddAction(null);
setDeletePointOrLine(false);
setRefTextUpdate((prev) => prev - 1);
};
const updateToolBehavior = (tool: string, is2D: boolean) => {
switch (tool) {
case "cursor":
if (toggleView) {
setToolMode("move");
}
break;
case "draw-wall":
is2D && setToolMode("Wall");
break;
2025-03-29 07:27:16 +00:00
case "draw-aisle":
is2D && setToolMode("Aisle");
break;
2025-03-29 07:27:16 +00:00
case "draw-zone":
is2D && setToolMode("Zone");
break;
2025-03-29 07:27:16 +00:00
case "draw-floor":
is2D && setToolMode("Floor");
break;
2025-03-29 07:27:16 +00:00
case "measure":
setToolMode("MeasurementScale");
break;
case "Add pillar":
if (!is2D) setAddAction("pillar");
break;
2025-03-29 07:27:16 +00:00
case "delete":
is2D ? setDeletePointOrLine(true) : setDeleteTool(true);
break;
}
};
const toggle2D3D = () => {
const toggleTo2D = toggleView;
setToggleView(!toggleTo2D);
setToggleThreeD(toggleTo2D);
setToggleUI(toggleTo2D, toggleTo2D);
if (toggleTo2D) {
setSelectedWallItem(null);
setDeleteTool(false);
setAddAction(null);
}
setActiveTool("cursor");
setActiveSubTool("cursor");
setToggleUI(
localStorage.getItem("navBarUiLeft") !== "false",
localStorage.getItem("navBarUiRight") !== "false"
);
};
const renderBuilderTools = () => (
<>
{!toggleThreeD && (
<div className="draw-tools">
<ToolButton
toolId="drawWall"
icon={WallIcon}
tooltip="draw wall (q)"
active={activeTool === "draw-wall"}
onClick={() => setActiveTool("draw-wall")}
/>
<ToolButton
toolId="drawZone"
icon={ZoneIcon}
tooltip="draw zone (e)"
active={activeTool === "draw-zone"}
onClick={() => setActiveTool("draw-zone")}
/>
<ToolButton
toolId="drawAisle"
icon={AsileIcon}
tooltip="draw aisle (r)"
active={activeTool === "draw-aisle"}
onClick={() => setActiveTool("draw-aisle")}
/>
<ToolButton
toolId="drawFloor"
icon={FloorIcon}
tooltip="draw floor (t)"
active={activeTool === "draw-floor"}
onClick={() => setActiveTool("draw-floor")}
/>
</div>
)}
<div className="draw-tools">
<ToolButton
toolId="measureScale"
icon={MeasureToolIcon}
tooltip="measure scale (m)"
active={activeTool === "measure"}
onClick={() => setActiveTool("measure")}
/>
</div>
</>
);
const renderSimulationTools = () => (
<div className="draw-tools">
<ToolButton
toolId="pen"
icon={PenIcon}
tooltip="pen"
active={activeTool === "pen"}
onClick={() => setActiveTool("pen")}
/>
</div>
);
const renderVisualizationTools = () => (
<div className="draw-tools">
<ToolButton
toolId="saveTemplate"
icon={SaveTemplateIcon}
tooltip="save template"
active={false}
onClick={() =>
handleSaveTemplate({
addTemplate,
floatingWidget,
widgets3D,
selectedZone,
templates,
visualizationSocket,
})
}
/>
</div>
);
const renderModeSwitcher = () => (
<button
id="toggle-threed-button"
className={`toggle-threed-button${toggleThreeD ? " toggled" : ""}`}
onClick={toggle2D3D}
>
<div className="tooltip">toggle view (tab)</div>
<div className={`toggle-option${!toggleThreeD ? " active" : ""}`}>2d</div>
<div className={`toggle-option${toggleThreeD ? " active" : ""}`}>3d</div>
</button>
);
const getIconByTool = (tool: string) => {
switch (tool) {
case "cursor":
return CursorIcon;
case "free-hand":
return FreeMoveIcon;
case "delete":
return DeleteIcon;
default:
return CursorIcon;
}
};
const getTooltipShortcut = (tool: string) => {
switch (tool) {
case "cursor":
return "v";
case "free-hand":
return "h";
case "delete":
return "x";
default:
return "";
}
};
const getIconComponent = (option: string) => {
switch (option) {
case "cursor":
return <CursorIcon isActive={false} />;
case "free-hand":
return <FreeMoveIcon isActive={false} />;
case "delete":
return <DeleteIcon isActive={false} />;
default:
return null;
}
};
2025-03-25 06:17:41 +00:00
return (
<div className={`tools-container ${showShortcuts ? "visible" : ""}`}>
<div className="activeDropicon">
{/* Tool Picker (cursor, delete, etc.) */}
{["cursor", "free-hand", "delete"].map(
(tool) =>
activeSubTool === tool && (
<ToolButton
key={tool}
toolId={tool}
icon={getIconByTool(tool)}
tooltip={`${tool} (${getTooltipShortcut(tool)})`}
active={activeTool === tool}
onClick={() => setActiveTool(tool)}
/>
)
)}
{/* Dropdown Menu */}
{activeModule !== "visualization" && (
<button
id="drop-down-button"
className="drop-down-option-button"
ref={dropdownRef}
onClick={() => setOpenDrop(!openDrop)}
>
<ArrowIcon />
{openDrop && (
<div className="drop-down-container">
{["cursor", "free-hand", "delete"].map((option) => (
<button
key={option}
id={`${option}-tool`}
className="option-list"
onClick={() => {
setActiveTool(option);
setActiveSubTool(option);
setOpenDrop(false);
}}
>
<div className="active-option">
{activeSubTool === option && <TickIcon />}
</div>
{getIconComponent(option)}
<div className="option">{option}</div>
</button>
))}
</div>
)}
</button>
)}
</div>
<div className="split"></div>
{activeModule === "builder" && renderBuilderTools()}
{activeModule === "simulation" && renderSimulationTools()}
{activeModule === "visualization" && renderVisualizationTools()}
<div className="split"></div>
<div className="general-options">
<ToolButton
toolId="comment"
icon={CommentIcon}
tooltip="comment"
active={activeTool === "comment"}
onClick={() => setActiveTool("comment")}
/>
{toggleThreeD && (
<ToolButton
toolId="play"
icon={PlayIcon}
tooltip="play (ctrl + p)"
active={activeTool === "play"}
onClick={() => setIsPlaying(!isPlaying)}
/>
)}
</div>
{activeModule === "builder" && (
2025-03-27 05:25:44 +00:00
<>
<div className="split"></div>
{renderModeSwitcher()}
2025-03-27 05:25:44 +00:00
</>
2025-03-26 06:00:17 +00:00
)}
</div>
2025-03-25 06:17:41 +00:00
);
};
// Extracted common store logic
const useStoreHooks = () => {
return {
...useActiveTool(),
...useToolMode(),
...useDeleteTool(),
...useAddAction(),
...useDeletePointOrLine(),
...useRefTextUpdate(),
};
};
2025-03-25 06:17:41 +00:00
export default Tools;