Merge branch 'main' into simulation-animation

This commit is contained in:
SreeNath14 2025-04-10 17:52:30 +05:30
commit 7af1724715
25 changed files with 441 additions and 217 deletions

View File

@ -1,10 +1,13 @@
# Project Folder Structure
# **Project Folder Structure Maintenance**
This document provides a detailed description of the purpose of each folder in the project by root level, along with the folder hierarchy.
This document outlines the projects folder structure, describing the purpose of each folder and file. It also includes guidelines for development, collaboration, and scalability.
## Folder Hierarchy
---
## **Folder Hierarchy**
```
📁 .github
📁 src
├── 📁 assets
├── 📁 components
@ -14,94 +17,195 @@ This document provides a detailed description of the purpose of each folder in t
│ ├── 📁 builder
│ ├── 📁 simulation
│ └── 📁 visualization
├── 📁 pages
├── 📁 services
├── 📁 store
├── 📁 styles
├── 📁 tests
├── 📁 types
├── 📁 utils
├── 📁 temp
├── App.css
├── App.tsx
├── index.css
├── main.tsx
└── vite-env.d.ts
├── Dockerfile
└── nginx.conf
```
---
## Description of Each Folder
## **Folder and File Descriptions**
### 📁 `src`
The root directory for all source code related to the application.
#### 📁 `assets`
- **Purpose:** Contains static assets such as images, icons, fonts, or other resources.
- **Example:**
- `react.svg`: A static SVG file used in the project.
#### 📁 `components`
- **Purpose:** Contains reusable React components, serving as building blocks for the user interface.
- **Example:**
- Buttons, modals, headers, and forms.
#### 📁 `functions`
- **Purpose:** Stores pure functions or logic that can be reused across the application.
- **Example:**
- Utility functions for data transformation or computation.
#### 📁 `hooks`
- **Purpose:** Holds custom React hooks for managing specific logic or behaviors.
- **Example:**
- Hooks for state management, API calls, or reusable effects.
#### 📁 `modules`
- **Purpose:** Organizes high-level, feature-specific code into subdirectories.
- **📁 builder:** Manages functionalities and components related to building or configuring features.
- **📁 simulation:** Handles processes or logic related to simulations or dynamic scenarios.
- **📁 visualization:** Focuses on displaying data visually, such as charts, graphs, or interactive UI elements.
#### 📁 `services`
- **Purpose:** Encapsulates external service interactions, such as API calls or library integrations.
- **Example:**
- REST API clients or authentication handlers.
#### 📁 `store`
- **Purpose:** Contains state management logic and configurations for tools like Redux or Zustand.
- **Example:**
- Redux slices, context providers, or global state stores.
#### 📁 `styles`
- **Purpose:** Includes global CSS, SCSS, or theming resources.
- **Example:**
- Global styles, theme variables, or resets.
#### 📁 `tests`
- **Purpose:** Stores test files for unit testing, integration testing, and mock data.
- **Example:**
- Test files (`*.test.tsx`, `*.spec.ts`) or mock utilities.
#### 📁 `types`
- **Purpose:** Contains shared TypeScript type definitions and interfaces.
- **Example:**
- Type declarations for props, data models, or API responses.
#### 📁 `utils`
- **Purpose:** Includes general-purpose utility files that dont fit into other specific folders.
- **Example:**
- Helper functions like debouncers or date formatters.
### 📁 **`src`**
The root directory for all application source code.
---
## Root-Level Files
### 📁 **`assets`**
- **Purpose:** Static assets like images, fonts, and icons.
- **Examples:**
- Subfolders for `images`, `icons`, and `fonts`.
- Use descriptive file names (e.g., `logo.svg`, `Roboto-Regular.ttf`).
### `App.tsx`
- **Purpose:** The root React component, initializing the main application layout and logic.
---
### `index.css`
- **Purpose:** Contains global styles applied throughout the application.
### 📁 **`components`**
- **Purpose:** Reusable React components forming the building blocks of the UI.
- **Examples:**
- Buttons, modals, input fields, and headers.
- Organize larger components into folders (e.g., `Button/`).
### `main.tsx`
- **Purpose:** The entry point of the app, rendering the React application and setting up the React DOM.
---
### `vite-env.d.ts`
- **Purpose:** TypeScript environment configuration file for Vite.
### 📁 **`functions`**
- **Purpose:** Pure functions and reusable logic.
- **Examples:**
- `formatDate.ts`: Format dates into readable strings.
- `calculateTotal.ts`: Logic for cart total calculation.
---
### 📁 **`hooks`**
- **Purpose:** Custom React hooks encapsulating reusable logic.
- **Examples:**
- `useAuth.ts`: Manages authentication logic.
- `useFetch.ts`: Fetches data from APIs.
---
### 📁 **`modules`**
- **Purpose:** Feature-specific code organized into subfolders.
- **Subfolders:**
- **`builder`**: For UI components and logic related to building features.
- **`simulation`**: Code for running simulations.
- **`visualization`**: Visualization components like charts and graphs.
---
### 📁 **`pages`**
- **Purpose:** Pages that represent routes in the application.
- **Examples:**
- `HomePage.tsx`: Home route.
- `DashboardPage.tsx`: Dashboard route.
---
### 📁 **`services`**
- **Purpose:** External API interactions and third-party service logic.
- **Examples:**
- `apiClient.ts`: Wrapper for HTTP requests.
- `authService.ts`: Authentication services.
---
### 📁 **`store`**
- **Purpose:** State management logic using tools like Redux or Context API.
- **Examples:**
- `authSlice.ts`: Authentication-related state management.
- `cartSlice.ts`: Shopping cart state management.
---
### 📁 **`styles`**
- **Purpose:** Global and modular styles.
- **Examples:**
- `global.css`: Global styles.
- `theme.scss`: Theme variables.
---
### 📁 **`tests`**
- **Purpose:** Test files for unit, integration, and E2E testing.
- **Examples:**
- `Button.test.tsx`: Tests for the `Button` component.
- `mockData.ts`: Mock API responses.
---
### 📁 **`types`**
- **Purpose:** Shared TypeScript type definitions and interfaces.
- **Examples:**
- `User.ts`: User-related type definitions.
- `ApiResponse.ts`: API response types.
---
### 📁 **`utils`**
- **Purpose:** General-purpose utility functions and constants.
- **Examples:**
- `debounce.ts`: Debounce function.
- `constants.ts`: Shared constants.
---
### 📁 **`temp`**
- **Purpose:** A temporary directory for experimental or work-in-progress code.
- **Guidelines:**
- This folder is included in `.gitignore` to prevent its contents from affecting the main project.
- Move any experimental work here to isolate it from production-ready code.
---
### **Root-Level Files**
- **`App.tsx`**: Root React component.
- **`main.tsx`**: Entry point for the app.
- **`Dockerfile`**: Docker configuration for containerizing the application.
- **`nginx.conf`**: Configuration for the Nginx server.
---
## **Development Guidelines**
### **Environment Management**
- **`.env.local`**: Use this file for testing and development-specific variables.
- **`.env`**: Should only contain variables required for deployed services (e.g., API base URLs).
### **Collaboration Best Practices**
1. **Use the `temp` Folder for Experiments:**
Any experimental work should be isolated in the `temp` folder. Avoid committing temporary code to the main repository.
2. **Documentation:**
- Read the shared documentation in the `docify` and `.github` folders before starting work.
- Follow any guidelines or standards outlined in these documents.
3. **Branching Rules:**
- Do not merge other branches into your branch without proper code review or necessity.
- Use meaningful branch names (e.g., `feature/auth-module`, `fix/header-bug`).
4. **Code Reviews:**
- Ensure all code undergoes peer review before merging.
- Use PR templates provided in the `.github` folder to document changes.
---
## **Additional Instructions for Large-Scale Projects**
1. **Folder Depth:**
- Avoid excessive nesting of folders to maintain simplicity.
- Refactor large folders into modules or domains as the project scales.
2. **Dependency Management:**
- Regularly review and update dependencies.
- Remove unused or deprecated libraries to reduce technical debt.
3. **Automate Workflows:**
- Use CI/CD pipelines to automate testing, building, and deployment processes.
- Integrate tools like Prettier, ESLint, and Husky to enforce code quality.
4. **Versioning and Change Logs:**
- Maintain a changelog to track major updates.
- Use semantic versioning (`x.y.z`) for releases.
5. **Performance Monitoring:**
- Regularly monitor and optimize app performance.
- Use tools like Lighthouse, React DevTools, and browser profilers.
6. **Error Handling:**
- Centralize error handling using utilities or middleware.
- Log errors in both the frontend and backend for debugging.
7. **Documentation:**
- Continuously update this document to reflect structural or procedural changes.
- Ensure all team members are familiar with the documentation.

View File

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

View File

Before

Width:  |  Height:  |  Size: 7.9 KiB

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 480 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

View File

@ -1,16 +1,20 @@
import React, { useEffect, useMemo, useState } from "react";
import Search from "../../ui/inputs/Search";
import vehicle from "../../../assets/image/vehicles.png";
import workStation from "../../../assets/image/workStation.png";
import machines from "../../../assets/image/machines.png";
import feneration from "../../../assets/image/feneration.png";
import worker from "../../../assets/image/worker.png";
import { getCategoryAsset } from "../../../services/factoryBuilder/assest/assets/getCategoryAsset";
import arch from "../../../assets/gltf-glb/arch.glb";
import door from "../../../assets/gltf-glb/door.glb";
import window from "../../../assets/gltf-glb/window.glb";
import { fetchAssets } from "../../../services/marketplace/fetchAssets";
import { useSelectedItem } from "../../../store/store";
// images -------------------
import vehicle from "../../../assets/image/categories/vehicles.png";
import workStation from "../../../assets/image/categories/workStation.png";
import machines from "../../../assets/image/categories/machines.png";
import feneration from "../../../assets/image/categories/feneration.png";
import worker from "../../../assets/image/categories/worker.png";
// -------------------------------------
interface AssetProp {
filename: string;
thumbnail?: string;

View File

@ -115,17 +115,10 @@ const Tools: React.FC = () => {
setOpenDrop(false); // Close the dropdown
}
};
const handleEscKeyPress = (event: KeyboardEvent) => {
if (event.key === "Escape") {
setIsPlaying(false); // Set isPlaying to false when Escape key is pressed
}
};
document.addEventListener("mousedown", handleOutsideClick);
document.addEventListener("keydown", handleEscKeyPress); // Listen for ESC key
return () => {
document.removeEventListener("mousedown", handleOutsideClick);
document.removeEventListener("keydown", handleEscKeyPress); // Clean up the event listener
};
}, []);
useEffect(() => {

View File

@ -5,6 +5,7 @@ import { useCamMode, useToggleView } from '../../../store/store';
import { useKeyboardControls } from '@react-three/drei';
import switchToThirdPerson from './switchToThirdPerson';
import switchToFirstPerson from './switchToFirstPerson';
import { detectModifierKeys } from '../../../utils/shortcutkeys/detectModifierKeys';
const CamMode: React.FC = () => {
const { camMode, setCamMode } = useCamMode();
@ -37,7 +38,9 @@ const CamMode: React.FC = () => {
const handleKeyPress = async (event: any) => {
if (!state.controls) return;
if (event.key === "/" && !isTransitioning && !toggleView) {
const keyCombination = detectModifierKeys(event);
if (keyCombination === "/" && !isTransitioning && !toggleView) {
setIsTransitioning(true);
state.controls.mouseButtons.left = CONSTANTS.controlsTransition.leftMouse;
state.controls.mouseButtons.right = CONSTANTS.controlsTransition.rightMouse;
@ -81,9 +84,7 @@ const CamMode: React.FC = () => {
}
});
return (
<></>
);
return null; // This component does not render any UI
};
export default CamMode;

View File

@ -5,6 +5,7 @@ import { useFloorItems, useSelectedAssets, useSimulationStates, useSocketStore,
import { toast } from "react-toastify";
// import { setFloorItemApi } from '../../../../services/factoryBuilder/assest/floorAsset/setFloorItemApi';
import * as Types from "../../../../types/world/worldTypes";
import { detectModifierKeys } from "../../../../utils/shortcutkeys/detectModifierKeys";
const CopyPasteControls = ({ itemsGroupRef, copiedObjects, setCopiedObjects, pastedObjects, setpastedObjects, selectionGroup, setDuplicatedObjects, movedObjects, setMovedObjects, rotatedObjects, setRotatedObjects, boundingBoxRef }: any) => {
const { camera, controls, gl, scene, pointer, raycaster } = useThree();
@ -38,10 +39,12 @@ const CopyPasteControls = ({ itemsGroupRef, copiedObjects, setCopiedObjects, pas
};
const onKeyDown = (event: KeyboardEvent) => {
if (event.ctrlKey && event.key.toLowerCase() === "c" && movedObjects.length === 0 && rotatedObjects.length === 0) {
const keyCombination = detectModifierKeys(event);
if (keyCombination === "Ctrl+C" && movedObjects.length === 0 && rotatedObjects.length === 0) {
copySelection();
}
if (event.ctrlKey && event.key.toLowerCase() === "v" && copiedObjects.length > 0 && pastedObjects.length === 0 && movedObjects.length === 0 && rotatedObjects.length === 0) {
if (keyCombination === "Ctrl+V" && copiedObjects.length > 0 && pastedObjects.length === 0 && movedObjects.length === 0 && rotatedObjects.length === 0) {
pasteCopiedObjects();
}
};
@ -570,9 +573,7 @@ const CopyPasteControls = ({ itemsGroupRef, copiedObjects, setCopiedObjects, pas
setSelectedAssets([]);
}
return (
<></>
);
return null; // No visible output, but the component handles copy-paste functionality
};
export default CopyPasteControls;

View File

@ -6,6 +6,7 @@ import { toast } from "react-toastify";
// import { setFloorItemApi } from '../../../../services/factoryBuilder/assest/floorAsset/setFloorItemApi';
import * as Types from "../../../../types/world/worldTypes";
import { setFloorItemApi } from "../../../../services/factoryBuilder/assest/floorAsset/setFloorItemApi";
import { detectModifierKeys } from "../../../../utils/shortcutkeys/detectModifierKeys";
const DuplicationControls = ({ itemsGroupRef, duplicatedObjects, setDuplicatedObjects, setpastedObjects, selectionGroup, movedObjects, setMovedObjects, rotatedObjects, setRotatedObjects, boundingBoxRef }: any) => {
const { camera, controls, gl, scene, pointer, raycaster } = useThree();
@ -39,12 +40,11 @@ const DuplicationControls = ({ itemsGroupRef, duplicatedObjects, setDuplicatedOb
};
const onKeyDown = (event: KeyboardEvent) => {
if (event.key.toLowerCase() === "d") {
event.preventDefault();
if (event.ctrlKey && event.key.toLowerCase() === "d" && selectedAssets.length > 0 && duplicatedObjects.length === 0 && movedObjects.length === 0 && rotatedObjects.length === 0) {
const keyCombination = detectModifierKeys(event);
if (keyCombination === "Ctrl+D" && selectedAssets.length > 0 && duplicatedObjects.length === 0 && movedObjects.length === 0 && rotatedObjects.length === 0) {
duplicateSelection();
}
}
};
if (!toggleView) {
@ -552,9 +552,7 @@ const DuplicationControls = ({ itemsGroupRef, duplicatedObjects, setDuplicatedOb
setSelectedAssets([]);
}
return (
<></>
);
return null; // This component does not render any UI
};
export default DuplicationControls;

View File

@ -5,6 +5,7 @@ import { useFloorItems, useSelectedAssets, useSimulationStates, useSocketStore,
// import { setFloorItemApi } from '../../../../services/factoryBuilder/assest/floorAsset/setFloorItemApi';
import { toast } from "react-toastify";
import * as Types from "../../../../types/world/worldTypes";
import { detectModifierKeys } from "../../../../utils/shortcutkeys/detectModifierKeys";
function MoveControls({ movedObjects, setMovedObjects, itemsGroupRef, copiedObjects, setCopiedObjects, pastedObjects, setpastedObjects, duplicatedObjects, setDuplicatedObjects, selectionGroup, rotatedObjects, setRotatedObjects, boundingBoxRef }: any) {
const { camera, controls, gl, scene, pointer, raycaster } = useThree();
@ -56,14 +57,16 @@ function MoveControls({ movedObjects, setMovedObjects, itemsGroupRef, copiedObje
};
const onKeyDown = (event: KeyboardEvent) => {
const keyCombination = detectModifierKeys(event);
if (pastedObjects.length > 0 || duplicatedObjects.length > 0 || rotatedObjects.length > 0) return;
if (event.key.toLowerCase() === "g") {
if (keyCombination === "G") {
if (selectedAssets.length > 0) {
moveAssets();
itemsData.current = floorItems.filter((item: { modeluuid: string }) => selectedAssets.some((asset: any) => asset.uuid === item.modeluuid));
}
}
if (event.key.toLowerCase() === "escape") {
if (keyCombination === "ESCAPE") {
event.preventDefault();
clearSelection();
@ -453,9 +456,7 @@ function MoveControls({ movedObjects, setMovedObjects, itemsGroupRef, copiedObje
setSelectedAssets([]);
}
return (
<></>
)
return null; // No need to return anything, as this component is used for its side effects
}
export default MoveControls

View File

@ -457,9 +457,7 @@ function RotateControls({ rotatedObjects, setRotatedObjects, movedObjects, setMo
setSelectedAssets([]);
}
return (
<></>
)
return null; // No need to return anything, as this component is used for its side effects
}
export default RotateControls

View File

@ -14,6 +14,7 @@ import CopyPasteControls from "./copyPasteControls";
import MoveControls from "./moveControls";
import RotateControls from "./rotateControls";
import useModuleStore from "../../../../store/useModuleStore";
import { detectModifierKeys } from "../../../../utils/shortcutkeys/detectModifierKeys";
const SelectionControls: React.FC = () => {
const { camera, controls, gl, scene, pointer } = useThree();
@ -101,12 +102,13 @@ const SelectionControls: React.FC = () => {
};
const onKeyDown = (event: KeyboardEvent) => {
const keyCombination = detectModifierKeys(event);
if (movedObjects.length > 0 || rotatedObjects.length > 0) return;
if (event.key.toLowerCase() === "escape") {
if (keyCombination === "ESCAPE") {
event.preventDefault();
clearSelection();
}
if (event.key.toLowerCase() === "delete") {
if (keyCombination === "DELETE") {
event.preventDefault();
deleteSelection();
}

View File

@ -16,6 +16,7 @@ import { useFrame, useThree } from "@react-three/fiber";
import { useSubModuleStore } from "../../../store/useModuleStore";
import { usePlayButtonStore } from "../../../store/usePlayButtonStore";
import { setEventApi } from "../../../services/factoryBuilder/assest/floorAsset/setEventsApt";
import { detectModifierKeys } from "../../../utils/shortcutkeys/detectModifierKeys";
function PathCreation({ pathsGroupRef, }: { pathsGroupRef: React.MutableRefObject<THREE.Group>; }) {
const { isPlaying } = usePlayButtonStore();
@ -40,11 +41,12 @@ function PathCreation({ pathsGroupRef, }: { pathsGroupRef: React.MutableRefObjec
useEffect(() => {
setTransformMode(null);
const handleKeyDown = (e: KeyboardEvent) => {
const keyCombination = detectModifierKeys(e);
if (!selectedActionSphere) return;
if (e.key === "g") {
if (keyCombination === "G") {
setTransformMode((prev) => (prev === "translate" ? null : "translate"));
}
if (e.key === "r") {
if (keyCombination === "R") {
setTransformMode((prev) => (prev === "rotate" ? null : "rotate"));
}
};

View File

@ -9,3 +9,25 @@
justify-content: space-between;
align-items: center;
}
// Array of base colors
$colors: (
#f5550b,
#1bac1b,
#0099ff,
#d4c927,
#8400ff,
#13e9b3,
#df1dcf
);
@mixin gradient-by-child($index) {
// Get the color based on the index passed
$base-color: nth($colors, $index);
// Apply gradient using the same color with different alpha values
background: linear-gradient(
144.19deg,
rgba($base-color, 0.2) 16.62%, // 20% opacity
rgba($base-color, 0.08) 85.81% // 80% opacity
);
}

View File

@ -7,14 +7,28 @@ input {
width: 100%;
padding: 2px 4px;
border-radius: #{$border-radius-small};
border: 1px solid var(--border-color);
outline: none;
outline: 2px solid var(--border-color);
outline-offset: -2px;
border: none;
background: transparent;
color: var(--input-text-color);
&:focus,
&:active {
border: 1px solid var(--accent-color);
outline: 1px solid var(--accent-color);
}
&:-webkit-autofill,
&:-webkit-autofill:hover,
&:-webkit-autofill:focus,
&:-webkit-autofill:active {
// Text styles
-webkit-text-fill-color: var(--input-text-color) !important;
caret-color: var(--input-text-color);
// Background styles
background-color: var(--background-color) !important;
-webkit-box-shadow: 0 0 0px 1000px var(--background-color) inset !important;
}
}
@ -615,6 +629,7 @@ input {
input {
border: none;
outline: none;
background: transparent;
&::placeholder {

View File

@ -78,19 +78,19 @@
min-height: 50vh;
max-height: 60vh;
}
.template-item {
border: 1px solid #e0e0e0;
border-radius: 8px;
padding: 1rem;
transition: box-shadow 0.3s ease;
}
.template-image-container {
position: relative;
padding-bottom: 56.25%; // 16:9 aspect ratio
}
.template-image {
position: absolute;
width: 100%;
@ -100,19 +100,19 @@
cursor: pointer;
transition: transform 0.3s ease;
}
.template-details {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 0.5rem;
}
.template-name {
cursor: pointer;
font-weight: 500;
}
.delete-button {
padding: 0.25rem 0.5rem;
background: #ff4444;
@ -122,14 +122,13 @@
cursor: pointer;
transition: opacity 0.3s ease;
}
.no-templates {
text-align: center;
color: #666;
padding: 2rem;
grid-column: 1 / -1;
}
.widget-left-sideBar {
min-height: 50vh;
@ -538,7 +537,6 @@
.floating {
width: 100%;
}
}
@ -1070,24 +1068,62 @@
.category-name {
position: relative;
z-index: 3;
font-size: var(--font-size-large);
font-size: var(--font-size-regular);
// -webkit-text-fill-color: transparent;
// -webkit-text-stroke: 1px black;
}
&::after {
content: "";
width: 50px;
height: 50px;
width: 60px;
height: 60px;
border-radius: 50%;
background-color: var(--circle-color, #000);
position: absolute;
top: 50%;
top: 60%;
right: -10px;
transform: translate(0, -50%);
background: linear-gradient(144.19deg,
#f1e7cd 16.62%,
#fffaef 85.81%);
}
&:nth-child(1), &:nth-child(9) {
&::after {
@include gradient-by-child(1); // First child uses the first color
}
}
&:nth-child(2), &:nth-child(10) {
&::after {
@include gradient-by-child(2); // Second child uses the second color
}
}
&:nth-child(3), &:nth-child(11) {
&::after {
@include gradient-by-child(3); // Third child uses the third color
}
}
&:nth-child(4), &:nth-child(12) {
&::after {
@include gradient-by-child(4); // Fourth child uses the fourth color
}
}
&:nth-child(5), &:nth-child(13) {
&::after {
@include gradient-by-child(5); // Fifth child uses the fifth color
}
}
&:nth-child(6), &:nth-child(14) {
&::after {
@include gradient-by-child(6); // Fifth child uses the fifth color
}
}
&:nth-child(7), &:nth-child(15) {
&::after {
@include gradient-by-child(7); // Fifth child uses the fifth color
}
}
.category-image {
@ -1114,30 +1150,47 @@
.assets {
width: 117px;
height: 95px;
border-radius: 3.59px;
border-radius: #{$border-radius-small};
background-color: var(--background-color-gray);
padding: 8px;
padding-top: 12px;
font-weight: $medium-weight;
position: relative;
overflow: hidden;
padding: 0;
&:hover {
.asset-name {
opacity: 1;
}
}
.asset-name {
position: relative;
position: absolute;
top: 0;
z-index: 3;
padding: 8px;
width: 100%;
font-size: var(--font-size-regular);
background: color-mix(
in srgb,
var(--background-color) 40%,
transparent
);
backdrop-filter: blur(5px);
opacity: 0;
transition: opacity 0.3s ease;
/* Added properties for ellipsis */
display: -webkit-box; /* Necessary for multiline truncation */
-webkit-line-clamp: 2; /* Number of lines to show */
-webkit-box-orient: vertical; /* Box orientation for the ellipsis */
overflow: hidden; /* Hide overflowing content */
text-overflow: ellipsis; /* Add ellipsis for truncated content */
}
.asset-image {
height: 100%;
width: 100%;
position: absolute;
// top: 50%;
// right: 5px;
// transform: translate(0, -50%);
top: 0;
left: 0;
z-index: 2;
object-fit: cover;
}
}
}
@ -1200,4 +1253,4 @@
.assets-wrapper {
margin: 0;
}
}
}

View File

@ -765,20 +765,19 @@
.editWidgetOptions {
position: absolute;
// top: 50%;
// left: 50%;
// transform: translate(-50%, -50%);
background-color: var(--background-color);
z-index: 3;
display: flex;
flex-direction: column;
border-radius: 6px;
overflow: hidden;
padding: 4px;
min-width: 150px;
.option {
padding: 8px 10px;
padding: 4px 10px;
border-radius: #{$border-radius-small};
color: var(--text-color);
cursor: pointer;
@ -791,7 +790,8 @@
color: #f65648;
&:hover {
background-color: #ffe3e0;
background-color: #f65648;
color: white;
}
}
}

View File

@ -0,0 +1,30 @@
// Function to detect if Shift, Ctrl, Alt, or combinations are pressed
// and return the corresponding key combination string
export const detectModifierKeys = (event: KeyboardEvent): string => {
const modifiers = [
event.ctrlKey ? "Ctrl" : "",
event.altKey ? "Alt" : "",
event.shiftKey ? "Shift" : "",
event.metaKey ? "Meta" : "" // Add support for Command/Win key
].filter(Boolean);
// Ignore modifier keys when they're pressed alone
const isModifierKey = [
"Control", "Shift", "Alt", "Meta",
"Ctrl", "AltGraph", "OS" // Additional modifier key aliases
].includes(event.key);
const mainKey = isModifierKey ? "" : event.key.toUpperCase();
// Handle special cases for keys with different representations
const normalizedKey = mainKey === " " ? "Space" : mainKey;
// Build the combination string
if (modifiers.length > 0 && normalizedKey) {
return `${modifiers.join("+")}+${normalizedKey}`;
} else if (modifiers.length > 0) {
return modifiers.join("+");
} else {
return normalizedKey;
}
};

View File

@ -1,85 +1,70 @@
// Importing React and useEffect from React library
import React, { useEffect } from "react";
// Importing the necessary hooks and types from React and TypeScript
// 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 { useActiveSubTool, useActiveTool, useAddAction, useDeleteTool, useSelectedWallItem, useToggleView, useToolMode } from "../../store/store";
import {
useActiveSubTool,
useActiveTool,
useAddAction,
useDeleteTool,
useSelectedWallItem,
useToggleView,
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 = () => {
// Function to detect if Shift, Ctrl, Alt, or combinations are pressed
const detectModifierKeys = (event: KeyboardEvent): string => {
const modifiers = [
event.ctrlKey ? "Ctrl" : "",
event.altKey ? "Alt" : "",
event.shiftKey ? "Shift" : "",
event.metaKey ? "Meta" : "" // Add support for Command/Win key
].filter(Boolean);
// 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
// Ignore modifier keys when they're pressed alone
const isModifierKey = [
"Control", "Shift", "Alt", "Meta",
"Ctrl", "AltGraph", "OS" // Additional modifier key aliases
].includes(event.key);
const mainKey = isModifierKey ? "" : event.key.toUpperCase();
// Handle special cases for keys with different representations
const normalizedKey = mainKey === " " ? "Space" : mainKey;
// Build the combination string
if (modifiers.length > 0 && normalizedKey) {
return `${modifiers.join("+")}+${normalizedKey}`;
} else if (modifiers.length > 0) {
return modifiers.join("+");
} else {
return normalizedKey;
}
};
// Importing the necessary hooks from the store
const { activeModule, setActiveModule } = useModuleStore();
const { setActiveSubTool } = useActiveSubTool();
const { toggleUI, setToggleUI } = useToggleStore();
const { setToggleThreeD } = useThreeDStore();
const { setToolMode } = useToolMode();
const { setIsPlaying } = usePlayButtonStore();
// wall options
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
// 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;
const isTyping =
activeElement instanceof HTMLInputElement ||
activeElement instanceof HTMLTextAreaElement ||
(activeElement && activeElement.getAttribute('contenteditable') === 'true');
// 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; // Don't trigger shortcuts while typing
return; // Skip shortcut handling while typing
}
// Detect the combination of keys pressed
const keyCombination = detectModifierKeys(event);
// Allow default behavior for F5 and F12
// Allow browser default behavior for specific keys (e.g., F5 for refresh)
if (["F5", "F11", "F12"].includes(event.key) || keyCombination === "Ctrl+R") {
return;
}
// Prevent default action for the key press
// Prevent the default browser action for other handled key presses
event.preventDefault();
// Detect the key combination pressed
if (keyCombination) {
// Check for specific key combinations to switch modules
// Switch between different modules (e.g., builder, simulation)
if (keyCombination === "1" && !toggleView) {
setActiveTool("cursor");
setActiveSubTool("cursor");
@ -102,12 +87,12 @@ const KeyPressListener: React.FC = () => {
setActiveModule("market");
}
// sidebar toggle
// Toggle UI visibility
if (keyCombination === "Ctrl+." && activeModule !== "market") {
setToggleUI(!toggleUI);
}
// tools toggle
// Tool selection shortcuts
if (keyCombination === "V") {
setActiveTool("cursor");
setActiveSubTool("cursor");
@ -121,14 +106,15 @@ const KeyPressListener: React.FC = () => {
setActiveSubTool("free-hand");
}
// player toggle
// Toggle play mode
if (keyCombination === "Ctrl+P" && !toggleView) {
setIsPlaying(true);
}
// builder key combination
// Builder-specific shortcuts
if (activeModule === "builder") {
if (keyCombination === "TAB") {
// Switch between 2D and 3D views
if (toggleView) {
setToggleView(false);
setToggleThreeD(true);
@ -142,7 +128,8 @@ const KeyPressListener: React.FC = () => {
setActiveTool("cursor");
}
}
// builder tools
// Wall-related tools
if (toggleView) {
if (keyCombination === "Q" || keyCombination === "6") {
setActiveTool("draw-wall");
@ -161,21 +148,34 @@ const KeyPressListener: React.FC = () => {
setToolMode("Floor");
}
}
// Measurement tool
if (keyCombination === "M") {
setActiveTool("measure");
setToolMode("MeasurementScale");
}
}
// Undo redo
// Undo and redo actions
if (keyCombination === "Ctrl+Z") {
// Handle undo action here
// Handle undo action
}
if (keyCombination === "Ctrl+Y" || keyCombination === "Ctrl+Shift+Z") {
// Handle redo action here
// Handle redo action
}
// cleanup function to remove event listener
// 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);
@ -183,16 +183,16 @@ const KeyPressListener: React.FC = () => {
}
};
// Add event listener for keydown
// Add keydown event listener
window.addEventListener("keydown", handleKeyPress);
// Cleanup function to remove event listener
// Cleanup function to remove the event listener
return () => {
window.removeEventListener("keydown", handleKeyPress);
};
}, [activeModule, toggleUI, toggleView]); // Empty dependency array ensures this runs only once
}, [activeModule, toggleUI, toggleView]); // Dependencies to reapply effect if these values change
return null; // No UI component to render, so return null
return null; // This component does not render any UI
};
export default KeyPressListener;