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 />
</button>
</div>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,7 +1,4 @@
// Importing React and useEffect from React library
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 useToggleStore from "../../store/useUIToggleStore";
import {
@ -14,185 +11,141 @@ import {
useToolMode,
} from "../../store/store";
import { usePlayButtonStore } from "../../store/usePlayButtonStore";
// Utility function to detect modifier keys (Ctrl, Alt, Shift) and key combinations
import { detectModifierKeys } from "./detectModifierKeys";
// KeyPressListener component to handle global keyboard shortcuts
const KeyPressListener: React.FC = () => {
// Accessing state and actions from different stores
const { activeModule, setActiveModule } = useModuleStore(); // Module management (e.g., builder, simulation, visualization)
const { setActiveSubTool } = useActiveSubTool(); // Sub-tool management
const { toggleUI, setToggleUI } = useToggleStore(); // UI visibility toggle
const { setToggleThreeD } = useThreeDStore(); // 3D view toggle
const { setToolMode } = useToolMode(); // Tool mode management
const { setIsPlaying } = usePlayButtonStore(); // Play button state management
const { activeModule, setActiveModule } = useModuleStore();
const { setActiveSubTool } = useActiveSubTool();
const { toggleUI, setToggleUI } = useToggleStore();
const { setToggleThreeD } = useThreeDStore();
const { setToolMode } = useToolMode();
const { setIsPlaying } = usePlayButtonStore();
const { toggleView, setToggleView } = useToggleView();
const { setDeleteTool } = useDeleteTool();
const { setAddAction } = useAddAction();
const { setSelectedWallItem } = useSelectedWallItem();
const { setActiveTool } = useActiveTool();
// Wall and tool-related actions
const { toggleView, setToggleView } = useToggleView(); // 2D/3D toggle state
const { setDeleteTool } = useDeleteTool(); // Delete tool action
const { setAddAction } = useAddAction(); // Add action management
const { setSelectedWallItem } = useSelectedWallItem(); // Selected wall item management
const { setActiveTool } = useActiveTool(); // Active tool management
const isTextInput = (element: Element | null): boolean =>
element instanceof HTMLInputElement ||
element instanceof HTMLTextAreaElement ||
element?.getAttribute("contenteditable") === "true";
const handleModuleSwitch = (keyCombination: string) => {
const modules: Record<string, string> = {
"1": "builder",
"2": "simulation",
"3": "visualization",
"4": "market",
};
const module = modules[keyCombination];
if (module && !toggleView) {
setActiveTool("cursor");
setActiveSubTool("cursor");
if (module === "market") setToggleUI(false);
setActiveModule(module);
}
};
const handlePrimaryTools = (key: string) => {
const toolMap: Record<string, string> = {
V: "cursor",
X: "delete",
H: "free-hand",
};
const tool = toolMap[key];
if (tool) {
setActiveTool(tool);
setActiveSubTool(tool);
}
};
const handleBuilderShortcuts = (key: string) => {
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;
}
// 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();
if (keyCombination === "Ctrl+\\") {
if (activeModule !== "market") setToggleUI(!toggleUI);
return;
}
// Active module selection (builder, simulation, etc.)
handleModuleSwitch(keyCombination);
// Common editing tools: cursor | delete | free-hand
handlePrimaryTools(keyCombination);
// Shortcuts specific to the builder module (e.g., drawing and measurement tools)
handleBuilderShortcuts(keyCombination);
// Shortcut to enter play mode
if (keyCombination === "Ctrl+P" && !toggleView) {
setIsPlaying(true);
}
if (keyCombination === "ESCAPE") {
setActiveTool("cursor");
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
}
};
// useEffect to manage global keyboard shortcuts
useEffect(() => {
// Function to handle keydown events
const handleKeyPress = (event: KeyboardEvent) => {
// Identify the currently focused element
const activeElement = document.activeElement;
// Check if the user is typing in an input field, textarea, or contenteditable element
const isTyping =
activeElement instanceof HTMLInputElement ||
activeElement instanceof HTMLTextAreaElement ||
(activeElement && activeElement.getAttribute("contenteditable") === "true");
if (isTyping) {
return; // Skip shortcut handling while typing
}
// Detect the combination of keys pressed
const keyCombination = detectModifierKeys(event);
// Allow browser default behavior for specific keys (e.g., F5 for refresh)
if (["F5", "F11", "F12"].includes(event.key) || keyCombination === "Ctrl+R") {
return;
}
// Prevent the default browser action for other handled key presses
event.preventDefault();
if (keyCombination) {
// Switch between different modules (e.g., builder, simulation)
if (keyCombination === "1" && !toggleView) {
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
if (keyCombination === "Ctrl+." && activeModule !== "market") {
setToggleUI(!toggleUI);
}
// Tool selection shortcuts
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) {
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") {
setActiveTool("cursor");
setIsPlaying(false);
}
}
};
// Add keydown event listener
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 () => {
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
return null;
};
export default KeyPressListener;