Refactor keyboard shortcut handling and improve module switching logic

This commit is contained in:
Vishnu 2025-05-08 13:42:42 +05:30
parent 8bf48bfcfe
commit a2480748a8
9 changed files with 174 additions and 204 deletions

View File

@ -28,7 +28,7 @@ const Header: React.FC = () => {
} }
}} }}
> >
<div className="tooltip">{toggleUI ? "Hide" : "Show"} sidebar (ctrl + .)</div> <div className="tooltip">{toggleUI ? "Hide" : "Show"} sidebar (ctrl + \)</div>
<ToggleSidebarIcon /> <ToggleSidebarIcon />
</button> </button>
</div> </div>

View File

@ -1,4 +1,4 @@
import React, { useEffect } from "react"; import React from "react";
import useModuleStore from "../../store/useModuleStore"; import useModuleStore from "../../store/useModuleStore";
import { import {
BuilderIcon, BuilderIcon,
@ -14,45 +14,60 @@ const ModuleToggle: React.FC = () => {
return ( return (
<div className="module-toggle-container"> <div className="module-toggle-container">
<div <button
className={`module-list ${activeModule === "builder" && "active"}`} className={`module-list ${activeModule === "builder" ? "active" : ""}`}
onClick={() => { onClick={() => {
setActiveModule("builder"); setActiveModule("builder");
setToggleUI(localStorage.getItem('navBarUi') ? localStorage.getItem('navBarUi') === 'true' : true) setToggleUI(
localStorage.getItem("navBarUi")
? localStorage.getItem("navBarUi") === "true"
: true
);
}} }}
> >
<div className="icon"> <div className="icon">
<BuilderIcon isActive={activeModule === "builder"} /> <BuilderIcon isActive={activeModule === "builder"} />
</div> </div>
<div className="module">Builder</div> <div className="module">Builder</div>
</div> </button>
<div <button
className={`module-list ${activeModule === "simulation" && "active"}`} className={`module-list ${
activeModule === "simulation" ? "active" : ""
}`}
onClick={() => { onClick={() => {
setActiveModule("simulation"); setActiveModule("simulation");
setToggleUI(localStorage.getItem('navBarUi') ? localStorage.getItem('navBarUi') === 'true' : true) setToggleUI(
localStorage.getItem("navBarUi")
? localStorage.getItem("navBarUi") === "true"
: true
);
}} }}
> >
<div className="icon"> <div className="icon">
<SimulationIcon isActive={activeModule === "simulation"} /> <SimulationIcon isActive={activeModule === "simulation"} />
</div> </div>
<div className="module">Simulation</div> <div className="module">Simulation</div>
</div> </button>
<div <button
className={`module-list ${activeModule === "visualization" && "active" className={`module-list ${
activeModule === "visualization" ? "active" : ""
}`} }`}
onClick={() => { onClick={() => {
setActiveModule("visualization"); setActiveModule("visualization");
setToggleUI(localStorage.getItem('navBarUi') ? localStorage.getItem('navBarUi') === 'true' : true) setToggleUI(
localStorage.getItem("navBarUi")
? localStorage.getItem("navBarUi") === "true"
: true
);
}} }}
> >
<div className="icon"> <div className="icon">
<VisualizationIcon isActive={activeModule === "visualization"} /> <VisualizationIcon isActive={activeModule === "visualization"} />
</div> </div>
<div className="module">Visualization</div> <div className="module">Visualization</div>
</div> </button>
<div <button
className={`module-list ${activeModule === "market" && "active"}`} className={`module-list ${activeModule === "market" ? "active" : ""}`}
onClick={() => { onClick={() => {
setActiveModule("market"); setActiveModule("market");
setToggleUI(false); setToggleUI(false);
@ -62,7 +77,7 @@ const ModuleToggle: React.FC = () => {
<CartIcon isActive={activeModule === "market"} /> <CartIcon isActive={activeModule === "market"} />
</div> </div>
<div className="module">Market Place</div> <div className="module">Market Place</div>
</div> </button>
</div> </div>
); );
}; };

View File

@ -103,10 +103,9 @@ const DropDownList: React.FC<DropDownListProps> = ({
return ( return (
<div className="dropdown-list-container"> <div className="dropdown-list-container">
<div className="head"> {/* eslint-disable-next-line */}
<button className="value" onClick={handleToggle}> <div className="head" onClick={handleToggle}>
{value} <div className="value">{value}</div>
</button>
<div className="options"> <div className="options">
{showFocusIcon && ( {showFocusIcon && (
<div className="focus option"> <div className="focus option">

View File

@ -44,7 +44,7 @@ const SimulationPlayer: React.FC = () => {
setReset(false); setReset(false);
}, 0) }, 0)
} }
}, [isReset]) }, [isReset, setReset])
// Button functions // Button functions
const handleReset = () => { const handleReset = () => {

View File

@ -12,6 +12,7 @@ import CollabUserIcon from "./collabUserIcon";
import useModuleStore from "../../../store/useModuleStore"; import useModuleStore from "../../../store/useModuleStore";
import { getAvatarColor } from "../functions/getAvatarColor"; import { getAvatarColor } from "../functions/getAvatarColor";
import { useSelectedUserStore } from "../../../store/useCollabStore"; import { useSelectedUserStore } from "../../../store/useCollabStore";
import { opacity } from "html2canvas/dist/types/css/property-descriptors/opacity";
const CamModelsGroup = () => { const CamModelsGroup = () => {
const navigate = useNavigate(); const navigate = useNavigate();
@ -277,6 +278,10 @@ const CamModelsGroup = () => {
textAlign: "center", textAlign: "center",
fontFamily: "Arial, sans-serif", fontFamily: "Arial, sans-serif",
display: `${activeModule !== "visualization" ? "" : "none"}`, display: `${activeModule !== "visualization" ? "" : "none"}`,
opacity: `${
selectedUser?.name !== cam.userData.userName ? 1 : 0
}`,
transition: "opacity .2s ease",
}} }}
position={[-0.015, 0, 0.7]} position={[-0.015, 0, 0.7]}
> >

View File

@ -19,13 +19,11 @@ const CamMode: React.FC = () => {
const handlePointerLockChange = async () => { const handlePointerLockChange = async () => {
if (document.pointerLockElement && !toggleView) { if (document.pointerLockElement && !toggleView) {
// Pointer is locked // Pointer is locked
} else { } else if (camMode === "FirstPerson" && !toggleView) {
// Pointer is unlocked // Pointer is unlocked
if (camMode === "FirstPerson" && !toggleView) {
setCamMode("ThirdPerson"); setCamMode("ThirdPerson");
await switchToThirdPerson(state.controls, state.camera); await switchToThirdPerson(state.controls, state.camera);
} }
}
}; };
document.addEventListener("pointerlockchange", handlePointerLockChange); document.addEventListener("pointerlockchange", handlePointerLockChange);

View File

@ -130,7 +130,7 @@ const Project: React.FC = () => {
<LogList /> <LogList />
</RenderOverlay> </RenderOverlay>
)} )}
{activeModule != "market" && <Footer />} {activeModule !== "market" && !selectedUser && <Footer />}
</div> </div>
); );
}; };

View File

@ -7,7 +7,7 @@
top: 0; top: 0;
left: 0; left: 0;
outline: 8px solid var(--user-color); outline: 8px solid var(--user-color);
outline-offset: -3px; outline-offset: -4px;
border-radius: #{$border-radius-xlarge}; border-radius: #{$border-radius-xlarge};
.follower-name { .follower-name {

View File

@ -1,7 +1,4 @@
// Importing React and useEffect from React library
import React, { useEffect } from "react"; import React, { useEffect } from "react";
// Importing the necessary hooks and types from the application's state management stores
import useModuleStore, { useThreeDStore } from "../../store/useModuleStore"; import useModuleStore, { useThreeDStore } from "../../store/useModuleStore";
import useToggleStore from "../../store/useUIToggleStore"; import useToggleStore from "../../store/useUIToggleStore";
import { import {
@ -14,185 +11,141 @@ import {
useToolMode, useToolMode,
} from "../../store/store"; } from "../../store/store";
import { usePlayButtonStore } from "../../store/usePlayButtonStore"; import { usePlayButtonStore } from "../../store/usePlayButtonStore";
// Utility function to detect modifier keys (Ctrl, Alt, Shift) and key combinations
import { detectModifierKeys } from "./detectModifierKeys"; import { detectModifierKeys } from "./detectModifierKeys";
// KeyPressListener component to handle global keyboard shortcuts
const KeyPressListener: React.FC = () => { const KeyPressListener: React.FC = () => {
// Accessing state and actions from different stores const { activeModule, setActiveModule } = useModuleStore();
const { activeModule, setActiveModule } = useModuleStore(); // Module management (e.g., builder, simulation, visualization) const { setActiveSubTool } = useActiveSubTool();
const { setActiveSubTool } = useActiveSubTool(); // Sub-tool management const { toggleUI, setToggleUI } = useToggleStore();
const { toggleUI, setToggleUI } = useToggleStore(); // UI visibility toggle const { setToggleThreeD } = useThreeDStore();
const { setToggleThreeD } = useThreeDStore(); // 3D view toggle const { setToolMode } = useToolMode();
const { setToolMode } = useToolMode(); // Tool mode management const { setIsPlaying } = usePlayButtonStore();
const { setIsPlaying } = usePlayButtonStore(); // Play button state management const { toggleView, setToggleView } = useToggleView();
const { setDeleteTool } = useDeleteTool();
const { setAddAction } = useAddAction();
const { setSelectedWallItem } = useSelectedWallItem();
const { setActiveTool } = useActiveTool();
// Wall and tool-related actions const isTextInput = (element: Element | null): boolean =>
const { toggleView, setToggleView } = useToggleView(); // 2D/3D toggle state element instanceof HTMLInputElement ||
const { setDeleteTool } = useDeleteTool(); // Delete tool action element instanceof HTMLTextAreaElement ||
const { setAddAction } = useAddAction(); // Add action management element?.getAttribute("contenteditable") === "true";
const { setSelectedWallItem } = useSelectedWallItem(); // Selected wall item management
const { setActiveTool } = useActiveTool(); // Active tool management
// useEffect to manage global keyboard shortcuts const handleModuleSwitch = (keyCombination: string) => {
useEffect(() => { const modules: Record<string, string> = {
// Function to handle keydown events "1": "builder",
const handleKeyPress = (event: KeyboardEvent) => { "2": "simulation",
// Identify the currently focused element "3": "visualization",
const activeElement = document.activeElement; "4": "market",
};
// Check if the user is typing in an input field, textarea, or contenteditable element const module = modules[keyCombination];
const isTyping = if (module && !toggleView) {
activeElement instanceof HTMLInputElement || setActiveTool("cursor");
activeElement instanceof HTMLTextAreaElement || setActiveSubTool("cursor");
(activeElement && activeElement.getAttribute("contenteditable") === "true"); if (module === "market") setToggleUI(false);
setActiveModule(module);
if (isTyping) {
return; // Skip shortcut handling while typing
} }
};
// Detect the combination of keys pressed const handlePrimaryTools = (key: string) => {
const keyCombination = detectModifierKeys(event); const toolMap: Record<string, string> = {
V: "cursor",
X: "delete",
H: "free-hand",
};
const tool = toolMap[key];
if (tool) {
setActiveTool(tool);
setActiveSubTool(tool);
}
};
// Allow browser default behavior for specific keys (e.g., F5 for refresh) const handleBuilderShortcuts = (key: string) => {
if (["F5", "F11", "F12"].includes(event.key) || keyCombination === "Ctrl+R") { if (activeModule !== "builder") return;
if (key === "TAB") {
const toggleTo2D = toggleView;
setToggleView(!toggleTo2D);
setToggleThreeD(toggleTo2D);
if (toggleTo2D) {
setSelectedWallItem(null);
setDeleteTool(false);
setAddAction(null);
}
setActiveTool("cursor");
setActiveSubTool("cursor");
return; return;
} }
// Prevent the default browser action for other handled key presses // These should only apply in 2D view
const twoDToolConfigs: Record<string, { tool: string; mode: string }> = {
Q: { tool: "draw-wall", mode: "Wall" },
"6": { tool: "draw-wall", mode: "Wall" },
R: { tool: "draw-aisle", mode: "Aisle" },
"7": { tool: "draw-aisle", mode: "Aisle" },
E: { tool: "draw-zone", mode: "Zone" },
"8": { tool: "draw-zone", mode: "Zone" },
T: { tool: "draw-floor", mode: "Floor" },
"9": { tool: "draw-floor", mode: "Floor" },
};
const config = twoDToolConfigs[key];
if (toggleView && config) {
setActiveTool(config.tool);
setToolMode(config.mode);
}
// Measurement tool should work in both 2D and 3D
if (key === "M") {
setActiveTool("measure");
setToolMode("MeasurementScale");
}
};
const handleKeyPress = (event: KeyboardEvent) => {
if (isTextInput(document.activeElement)) return;
const keyCombination = detectModifierKeys(event);
if (!keyCombination || ["F5", "F11", "F12"].includes(event.key) || keyCombination === "Ctrl+R") return;
event.preventDefault(); event.preventDefault();
if (keyCombination) { if (keyCombination === "Ctrl+\\") {
// Switch between different modules (e.g., builder, simulation) if (activeModule !== "market") setToggleUI(!toggleUI);
if (keyCombination === "1" && !toggleView) { return;
setActiveTool("cursor");
setActiveSubTool("cursor");
setActiveModule("builder");
}
if (keyCombination === "2" && !toggleView) {
setActiveTool("cursor");
setActiveSubTool("cursor");
setActiveModule("simulation");
}
if (keyCombination === "3" && !toggleView) {
setActiveTool("cursor");
setActiveSubTool("cursor");
setActiveModule("visualization");
}
if (keyCombination === "4" && !toggleView) {
setActiveTool("cursor");
setActiveSubTool("cursor");
setToggleUI(false);
setActiveModule("market");
} }
// Toggle UI visibility // Active module selection (builder, simulation, etc.)
if (keyCombination === "Ctrl+." && activeModule !== "market") { handleModuleSwitch(keyCombination);
setToggleUI(!toggleUI); // Common editing tools: cursor | delete | free-hand
} handlePrimaryTools(keyCombination);
// Shortcuts specific to the builder module (e.g., drawing and measurement tools)
handleBuilderShortcuts(keyCombination);
// Tool selection shortcuts // Shortcut to enter play mode
if (keyCombination === "V") {
setActiveTool("cursor");
setActiveSubTool("cursor");
}
if (keyCombination === "X") {
setActiveTool("delete");
setActiveSubTool("delete");
}
if (keyCombination === "H") {
setActiveTool("free-hand");
setActiveSubTool("free-hand");
}
// Toggle play mode
if (keyCombination === "Ctrl+P" && !toggleView) { if (keyCombination === "Ctrl+P" && !toggleView) {
setIsPlaying(true); setIsPlaying(true);
} }
// Builder-specific shortcuts
if (activeModule === "builder") {
if (keyCombination === "TAB") {
// Switch between 2D and 3D views
if (toggleView) {
setToggleView(false);
setToggleThreeD(true);
setActiveTool("cursor");
} else {
setSelectedWallItem(null);
setDeleteTool(false);
setAddAction(null);
setToggleView(true);
setToggleThreeD(false);
setActiveTool("cursor");
}
}
// Wall-related tools
if (toggleView) {
if (keyCombination === "Q" || keyCombination === "6") {
setActiveTool("draw-wall");
setToolMode("Wall");
}
if (keyCombination === "R" || keyCombination === "7") {
setActiveTool("draw-aisle");
setToolMode("Aisle");
}
if (keyCombination === "E" || keyCombination === "8") {
setActiveTool("draw-zone");
setToolMode("Zone");
}
if (keyCombination === "T" || keyCombination === "9") {
setActiveTool("draw-floor");
setToolMode("Floor");
}
}
// Measurement tool
if (keyCombination === "M") {
setActiveTool("measure");
setToolMode("MeasurementScale");
}
}
// Undo and redo actions
if (keyCombination === "Ctrl+Z") {
// Handle undo action
}
if (keyCombination === "Ctrl+Y" || keyCombination === "Ctrl+Shift+Z") {
// Handle redo action
}
// Helper actions
if (keyCombination === "Ctrl+H") {
// Open help
}
if (keyCombination === "Ctrl+F") {
// Open find functionality
}
if (keyCombination === "Ctrl+?") {
// Show shortcuts info
}
// Reset to cursor tool and stop play mode
if (keyCombination === "ESCAPE") { if (keyCombination === "ESCAPE") {
setActiveTool("cursor"); setActiveTool("cursor");
setIsPlaying(false); setIsPlaying(false);
} }
// Placeholder for future implementation
if (["Ctrl+Z", "Ctrl+Y", "Ctrl+Shift+Z", "Ctrl+H", "Ctrl+F", "Ctrl+?"].includes(keyCombination)) {
// Implement undo/redo/help/find/shortcuts
} }
}; };
// Add keydown event listener useEffect(() => {
window.addEventListener("keydown", handleKeyPress); window.addEventListener("keydown", handleKeyPress);
return () => window.removeEventListener("keydown", handleKeyPress);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [activeModule, toggleUI, toggleView]);
// Cleanup function to remove the event listener return null;
return () => {
window.removeEventListener("keydown", handleKeyPress);
};
}, [activeModule, setActiveModule, setActiveSubTool, setActiveTool, setAddAction, setDeleteTool, setIsPlaying, setSelectedWallItem, setToggleThreeD, setToggleUI, setToggleView, setToolMode, toggleUI, toggleView]); // Dependencies to reapply effect if these values change
return null; // This component does not render any UI
}; };
export default KeyPressListener; export default KeyPressListener;