Refactor keyboard shortcut handling and improve module switching logic
This commit is contained in:
parent
8bf48bfcfe
commit
a2480748a8
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -44,7 +44,7 @@ const SimulationPlayer: React.FC = () => {
|
|||
setReset(false);
|
||||
}, 0)
|
||||
}
|
||||
}, [isReset])
|
||||
}, [isReset, setReset])
|
||||
|
||||
// Button functions
|
||||
const handleReset = () => {
|
||||
|
|
|
@ -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]}
|
||||
>
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -130,7 +130,7 @@ const Project: React.FC = () => {
|
|||
<LogList />
|
||||
</RenderOverlay>
|
||||
)}
|
||||
{activeModule != "market" && <Footer />}
|
||||
{activeModule !== "market" && !selectedUser && <Footer />}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue