main #6
1
app/.gitignore
vendored
@@ -2,6 +2,7 @@
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
# /package-lock.json
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
echo "Running pre-commit hook..."
|
||||
|
||||
# Compile TypeScript
|
||||
echo "Compiling TypeScript..."
|
||||
npx tsc scripts/validate-filenames.ts
|
||||
|
||||
# if [ $? -ne 0 ]; then
|
||||
# echo "TypeScript compilation failed. Aborting commit."
|
||||
# exit 1
|
||||
# fi
|
||||
|
||||
echo "TypeScript compilation successful."
|
||||
|
||||
# Run Node.js script
|
||||
echo "Running Node.js script..."
|
||||
node scripts/validate-filenames.js --no-prompt
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Node.js script failed. Aborting commit."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Pre-commit hook completed successfully."
|
||||
#!/usr/bin/env bash
|
||||
|
||||
echo "Running pre-commit hook..."
|
||||
|
||||
# Compile TypeScript
|
||||
echo "Compiling TypeScript..."
|
||||
npx tsc scripts/validate-filenames.ts
|
||||
|
||||
# if [ $? -ne 0 ]; then
|
||||
# echo "TypeScript compilation failed. Aborting commit."
|
||||
# exit 1
|
||||
# fi
|
||||
|
||||
echo "TypeScript compilation successful."
|
||||
|
||||
# Run Node.js script
|
||||
echo "Running Node.js script..."
|
||||
node scripts/validate-filenames.js --no-prompt
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Node.js script failed. Aborting commit."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Pre-commit hook completed successfully."
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
echo "Running pre-commit hook..."
|
||||
|
||||
# Compile TypeScript
|
||||
echo "Compiling TypeScript..."
|
||||
npx tsc scripts/validate-filenames.ts
|
||||
|
||||
# if [ $? -ne 0 ]; then
|
||||
# echo "TypeScript compilation failed. Aborting commit."
|
||||
# exit 1
|
||||
# fi
|
||||
|
||||
echo "TypeScript compilation successful."
|
||||
|
||||
# Run Node.js script
|
||||
echo "Running Node.js script..."
|
||||
node scripts/validate-filenames.js --no-prompt
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Node.js script failed. Aborting commit."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Pre-commit hook completed successfully."
|
||||
#!/usr/bin/env bash
|
||||
|
||||
echo "Running pre-commit hook..."
|
||||
|
||||
# Compile TypeScript
|
||||
echo "Compiling TypeScript..."
|
||||
npx tsc scripts/validate-filenames.ts
|
||||
|
||||
# if [ $? -ne 0 ]; then
|
||||
# echo "TypeScript compilation failed. Aborting commit."
|
||||
# exit 1
|
||||
# fi
|
||||
|
||||
echo "TypeScript compilation successful."
|
||||
|
||||
# Run Node.js script
|
||||
echo "Running Node.js script..."
|
||||
node scripts/validate-filenames.js --no-prompt
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Node.js script failed. Aborting commit."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Pre-commit hook completed successfully."
|
||||
|
||||
102
app/Dockerfile
@@ -1,51 +1,51 @@
|
||||
# Use the argument for node version (defaults to 'lts' if not provided)
|
||||
ARG NODE_VERSION=lts
|
||||
|
||||
# Stage 1: Build React App
|
||||
FROM node:${NODE_VERSION}-alpine AS development
|
||||
|
||||
# Set the Node.js environment to development
|
||||
ENV NODE_ENV=development
|
||||
|
||||
# Define build arguments for the API base URLs
|
||||
ARG REACT_APP_SERVER_SOCKET_API_BASE_URL
|
||||
ARG REACT_APP_SERVER_REST_API_BASE_URL
|
||||
ARG REACT_APP_SERVER_MARKETPLACE_URL
|
||||
|
||||
# Set environment variables for the API base URLs using the build arguments
|
||||
ENV REACT_APP_SERVER_SOCKET_API_BASE_URL=${REACT_APP_SERVER_SOCKET_API_BASE_URL}
|
||||
ENV REACT_APP_SERVER_REST_API_BASE_URL=${REACT_APP_SERVER_REST_API_BASE_URL}
|
||||
ENV REACT_APP_SERVER_MARKETPLACE_URL=${REACT_APP_SERVER_MARKETPLACE_URL}
|
||||
|
||||
# Set working directory for frontend code
|
||||
WORKDIR /frontend
|
||||
|
||||
# Copy package.json and package-lock.json for npm install
|
||||
COPY package*.json ./
|
||||
|
||||
# Install the latest npm version
|
||||
RUN npm install -g npm
|
||||
|
||||
# Install dependencies (this includes react-scripts)
|
||||
RUN npm install --legacy-peer-deps
|
||||
|
||||
# Copy the rest of the application code
|
||||
COPY . .
|
||||
|
||||
# Run the build command (build the React app)
|
||||
RUN npm run build
|
||||
|
||||
# Stage 2: Serve with Nginx
|
||||
FROM nginx:alpine
|
||||
|
||||
# Copy the built React files from the build stage into Nginx's default HTML folder
|
||||
COPY --from=development /frontend/build /usr/share/nginx/html
|
||||
|
||||
# Optionally copy a custom Nginx config (if needed)
|
||||
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
||||
|
||||
# Expose port 80 for Nginx (default HTTP port)
|
||||
EXPOSE 80
|
||||
|
||||
# Start Nginx in the foreground (this is required to keep the container running)
|
||||
CMD ["nginx", "-g", "daemon off;"]
|
||||
# Use the argument for node version (defaults to 'lts' if not provided)
|
||||
ARG NODE_VERSION=lts
|
||||
|
||||
# Stage 1: Build React App
|
||||
FROM node:${NODE_VERSION}-alpine AS development
|
||||
|
||||
# Set the Node.js environment to development
|
||||
ENV NODE_ENV=development
|
||||
|
||||
# Define build arguments for the API base URLs
|
||||
ARG REACT_APP_SERVER_SOCKET_API_BASE_URL
|
||||
ARG REACT_APP_SERVER_REST_API_BASE_URL
|
||||
ARG REACT_APP_SERVER_MARKETPLACE_URL
|
||||
|
||||
# Set environment variables for the API base URLs using the build arguments
|
||||
ENV REACT_APP_SERVER_SOCKET_API_BASE_URL=${REACT_APP_SERVER_SOCKET_API_BASE_URL}
|
||||
ENV REACT_APP_SERVER_REST_API_BASE_URL=${REACT_APP_SERVER_REST_API_BASE_URL}
|
||||
ENV REACT_APP_SERVER_MARKETPLACE_URL=${REACT_APP_SERVER_MARKETPLACE_URL}
|
||||
|
||||
# Set working directory for frontend code
|
||||
WORKDIR /frontend
|
||||
|
||||
# Copy package.json and package-lock.json for npm install
|
||||
COPY package*.json ./
|
||||
|
||||
# Install the latest npm version
|
||||
RUN npm install -g npm
|
||||
|
||||
# Install dependencies (this includes react-scripts)
|
||||
RUN npm install --legacy-peer-deps
|
||||
|
||||
# Copy the rest of the application code
|
||||
COPY . .
|
||||
|
||||
# Run the build command (build the React app)
|
||||
RUN npm run build
|
||||
|
||||
# Stage 2: Serve with Nginx
|
||||
FROM nginx:alpine
|
||||
|
||||
# Copy the built React files from the build stage into Nginx's default HTML folder
|
||||
COPY --from=development /frontend/build /usr/share/nginx/html
|
||||
|
||||
# Optionally copy a custom Nginx config (if needed)
|
||||
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
||||
|
||||
# Expose port 80 for Nginx (default HTTP port)
|
||||
EXPOSE 80
|
||||
|
||||
# Start Nginx in the foreground (this is required to keep the container running)
|
||||
CMD ["nginx", "-g", "daemon off;"]
|
||||
|
||||
198
app/README.md
@@ -1,99 +1,99 @@
|
||||
## Getting Started
|
||||
|
||||
Follow these steps to set up and run the project locally.
|
||||
|
||||
### Prerequisites
|
||||
|
||||
Ensure you have the following installed on your system:
|
||||
|
||||
- **Node.js**: [Download and install Node.js](https://nodejs.org/)
|
||||
- **npm**: Comes with Node.js, but you can also install [npm separately](https://www.npmjs.com/get-npm)
|
||||
- **yarn (optional)**: If you prefer to use Yarn, [install it here](https://yarnpkg.com/)
|
||||
- **TypeScript**: This project uses TypeScript, and the necessary dependencies will be installed automatically.
|
||||
|
||||
### Installation
|
||||
|
||||
1. Clone the repository:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/S0Vishnu/react-production-project-boilerplate.git
|
||||
cd react-production-project-boilerplate
|
||||
```
|
||||
|
||||
2. Cloning repository with User Credentials:
|
||||
|
||||
```bash
|
||||
git clone https://your_username:password@github.com/S0Vishnu/react-production-project-boilerplate.git
|
||||
cd react-production-project-boilerplate
|
||||
```
|
||||
|
||||
note: if password contains special charecters use:
|
||||
|
||||
- @ → %40
|
||||
- : → %3A
|
||||
- / → %2F
|
||||
- ? → %3F
|
||||
- & → %26
|
||||
- = → %3D
|
||||
- ! → %21
|
||||
|
||||
2. Install the dependencies:
|
||||
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
3. Start server:
|
||||
|
||||
```bash
|
||||
npm start
|
||||
```
|
||||
|
||||
4. Build the app for production:
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
5. Tests
|
||||
|
||||
This project includes both **unit tests** using **Jest** and **end-to-end (E2E) tests** using **Cypress**. Here’s how you can run and manage these tests.
|
||||
|
||||
**Unit Tests (Jest)**
|
||||
Unit tests are located in the `src/tests/unit/` directory. They test individual components and functions to ensure they work as expected. **Jest** is used for running these tests.
|
||||
|
||||
**Running Unit Tests**
|
||||
To run the unit tests, use the following command:
|
||||
|
||||
```bash
|
||||
npm run test
|
||||
```
|
||||
|
||||
**End-to-End (E2E) Tests (Cypress)**
|
||||
Cypress can be run in two modes
|
||||
|
||||
1. Interactive Mode:
|
||||
|
||||
```bash
|
||||
npm run cypress:open
|
||||
```
|
||||
|
||||
2. Headless Mode:
|
||||
|
||||
```bash
|
||||
npm run cypress:run
|
||||
```
|
||||
|
||||
### Run Documentation(Docsify)
|
||||
|
||||
1. Installation (if needed):
|
||||
|
||||
```bash
|
||||
npm i docsify-cli -g
|
||||
```
|
||||
|
||||
2. Run Command:
|
||||
|
||||
```bash
|
||||
docsify serve docs
|
||||
```
|
||||
## Getting Started
|
||||
|
||||
Follow these steps to set up and run the project locally.
|
||||
|
||||
### Prerequisites
|
||||
|
||||
Ensure you have the following installed on your system:
|
||||
|
||||
- **Node.js**: [Download and install Node.js](https://nodejs.org/)
|
||||
- **npm**: Comes with Node.js, but you can also install [npm separately](https://www.npmjs.com/get-npm)
|
||||
- **yarn (optional)**: If you prefer to use Yarn, [install it here](https://yarnpkg.com/)
|
||||
- **TypeScript**: This project uses TypeScript, and the necessary dependencies will be installed automatically.
|
||||
|
||||
### Installation
|
||||
|
||||
1. Clone the repository:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/S0Vishnu/react-production-project-boilerplate.git
|
||||
cd react-production-project-boilerplate
|
||||
```
|
||||
|
||||
2. Cloning repository with User Credentials:
|
||||
|
||||
```bash
|
||||
git clone https://your_username:password@github.com/S0Vishnu/react-production-project-boilerplate.git
|
||||
cd react-production-project-boilerplate
|
||||
```
|
||||
|
||||
note: if password contains special charecters use:
|
||||
|
||||
- @ → %40
|
||||
- : → %3A
|
||||
- / → %2F
|
||||
- ? → %3F
|
||||
- & → %26
|
||||
- = → %3D
|
||||
- ! → %21
|
||||
|
||||
2. Install the dependencies:
|
||||
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
3. Start server:
|
||||
|
||||
```bash
|
||||
npm start
|
||||
```
|
||||
|
||||
4. Build the app for production:
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
5. Tests
|
||||
|
||||
This project includes both **unit tests** using **Jest** and **end-to-end (E2E) tests** using **Cypress**. Here’s how you can run and manage these tests.
|
||||
|
||||
**Unit Tests (Jest)**
|
||||
Unit tests are located in the `src/tests/unit/` directory. They test individual components and functions to ensure they work as expected. **Jest** is used for running these tests.
|
||||
|
||||
**Running Unit Tests**
|
||||
To run the unit tests, use the following command:
|
||||
|
||||
```bash
|
||||
npm run test
|
||||
```
|
||||
|
||||
**End-to-End (E2E) Tests (Cypress)**
|
||||
Cypress can be run in two modes
|
||||
|
||||
1. Interactive Mode:
|
||||
|
||||
```bash
|
||||
npm run cypress:open
|
||||
```
|
||||
|
||||
2. Headless Mode:
|
||||
|
||||
```bash
|
||||
npm run cypress:run
|
||||
```
|
||||
|
||||
### Run Documentation(Docsify)
|
||||
|
||||
1. Installation (if needed):
|
||||
|
||||
```bash
|
||||
npm i docsify-cli -g
|
||||
```
|
||||
|
||||
2. Run Command:
|
||||
|
||||
```bash
|
||||
docsify serve docs
|
||||
```
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
# Boilerplate Project
|
||||
|
||||
A **Boilerplate Project** designed to jumpstart your development process with a clean, organized, and scalable structure.
|
||||
|
||||
## Features
|
||||
|
||||
- **Pre-configured Tooling**: Includes [Tool1], [Tool2], and [Tool3] for seamless development.
|
||||
- **Modular Structure**: Organized folder structure for scalability and maintainability.
|
||||
- **Best Practices**: Follows industry standards for coding, linting, and testing.
|
||||
- **Cross-Environment Compatibility**: Ready for local, staging, and production environments.
|
||||
|
||||
## Technologies Used
|
||||
|
||||
- **Frontend**: React
|
||||
- **Backend**: Node.js
|
||||
- **Database**: [MongoDB, PostgreSQL, MySQL, etc.]
|
||||
- **Build Tools**: Webpack
|
||||
- **Testing**: Jest
|
||||
# Boilerplate Project
|
||||
|
||||
A **Boilerplate Project** designed to jumpstart your development process with a clean, organized, and scalable structure.
|
||||
|
||||
## Features
|
||||
|
||||
- **Pre-configured Tooling**: Includes [Tool1], [Tool2], and [Tool3] for seamless development.
|
||||
- **Modular Structure**: Organized folder structure for scalability and maintainability.
|
||||
- **Best Practices**: Follows industry standards for coding, linting, and testing.
|
||||
- **Cross-Environment Compatibility**: Ready for local, staging, and production environments.
|
||||
|
||||
## Technologies Used
|
||||
|
||||
- **Frontend**: React
|
||||
- **Backend**: Node.js
|
||||
- **Database**: [MongoDB, PostgreSQL, MySQL, etc.]
|
||||
- **Build Tools**: Webpack
|
||||
- **Testing**: Jest
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
- [Get Started](/README.md)
|
||||
|
||||
<!-- Documetations Section (start)-->
|
||||
|
||||
<!-- Documetations Section (end)-->
|
||||
|
||||
- [Git Commands](./documents/gitNotes.md)
|
||||
- [Notes and Guidlines](./documents/notes.md)
|
||||
- [Project Structure](./documents/projectStructure.md)
|
||||
- [How to Document](./documents/documentationGuide.md)
|
||||
- [How to Write a Markdown (.md)](./documents/markdownGuide.md)
|
||||
- [Docsify Overview](./documents/docsifyGuide.md)
|
||||
- [Get Started](/README.md)
|
||||
|
||||
<!-- Documetations Section (start)-->
|
||||
|
||||
<!-- Documetations Section (end)-->
|
||||
|
||||
- [Git Commands](./documents/gitNotes.md)
|
||||
- [Notes and Guidlines](./documents/notes.md)
|
||||
- [Project Structure](./documents/projectStructure.md)
|
||||
- [How to Document](./documents/documentationGuide.md)
|
||||
- [How to Write a Markdown (.md)](./documents/markdownGuide.md)
|
||||
- [Docsify Overview](./documents/docsifyGuide.md)
|
||||
|
||||
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
@@ -1,35 +1,35 @@
|
||||
# Quick start
|
||||
|
||||
It is recommended to install `docsify-cli` globally, which helps initializing and previewing the website locally.
|
||||
|
||||
```bash
|
||||
npm i docsify-cli -g
|
||||
```
|
||||
|
||||
### Initialize
|
||||
|
||||
If you want to write the documentation in the `./docs` subdirectory, you can use the `init` command.
|
||||
|
||||
```bash
|
||||
docsify init ./docs
|
||||
```
|
||||
|
||||
### Writing content
|
||||
|
||||
After the `init` is complete, you can see the file list in the `./docs` subdirectory.
|
||||
|
||||
- `index.html` as the entry file
|
||||
- `README.md` as the home page
|
||||
- `.nojekyll` prevents GitHub Pages from ignoring files that begin with an underscore
|
||||
|
||||
You can easily update the documentation in `./docs/README.md`, of course you can add more pages.
|
||||
|
||||
### Preview your site
|
||||
|
||||
Run the local server with `docsify serve`. You can preview your site in your browser on `http://localhost:3000`.
|
||||
|
||||
```bash
|
||||
docsify serve docs
|
||||
```
|
||||
|
||||
# Quick start
|
||||
|
||||
It is recommended to install `docsify-cli` globally, which helps initializing and previewing the website locally.
|
||||
|
||||
```bash
|
||||
npm i docsify-cli -g
|
||||
```
|
||||
|
||||
### Initialize
|
||||
|
||||
If you want to write the documentation in the `./docs` subdirectory, you can use the `init` command.
|
||||
|
||||
```bash
|
||||
docsify init ./docs
|
||||
```
|
||||
|
||||
### Writing content
|
||||
|
||||
After the `init` is complete, you can see the file list in the `./docs` subdirectory.
|
||||
|
||||
- `index.html` as the entry file
|
||||
- `README.md` as the home page
|
||||
- `.nojekyll` prevents GitHub Pages from ignoring files that begin with an underscore
|
||||
|
||||
You can easily update the documentation in `./docs/README.md`, of course you can add more pages.
|
||||
|
||||
### Preview your site
|
||||
|
||||
Run the local server with `docsify serve`. You can preview your site in your browser on `http://localhost:3000`.
|
||||
|
||||
```bash
|
||||
docsify serve docs
|
||||
```
|
||||
|
||||
?> For more use cases of `docsify-cli`, head over to the [docsify-cli documentation](https://github.com/docsifyjs/docsify-cli).
|
||||
@@ -1,380 +1,380 @@
|
||||
# Why Documentation is Important for Developers
|
||||
|
||||
Documentation helps developers work efficiently and ensures projects run smoothly. Here’s why it’s crucial:
|
||||
|
||||
1. **Knowledge Sharing**: It helps team members understand the system and communicate effectively, especially when new people join the project.
|
||||
|
||||
2. **Code Maintenance**: Good documentation makes it easier to fix bugs, make updates, and keep the code consistent.
|
||||
|
||||
3. **Project Longevity**: It ensures that project knowledge is preserved, so future developers can maintain or improve the code without confusion.
|
||||
|
||||
4. **Troubleshooting**: Developers can quickly find solutions to problems or understand past decisions, saving time and reducing errors.
|
||||
|
||||
5. **Testing and Quality**: Documentation helps ensure the right testing processes are followed, leading to better-quality code.
|
||||
|
||||
6. **Efficiency**: It saves time by reducing the need to explain things repeatedly or search for answers.
|
||||
|
||||
In short, internal documentation keeps projects organized, helps teams collaborate, and ensures the software can be maintained and improved over time.
|
||||
|
||||
---
|
||||
|
||||
## Guide to Writing Modular Documentation for React Projects
|
||||
|
||||
Modular documentation refers to organizing your documentation into independent, reusable sections or modules. Each module typically covers a specific part of your project, making it easier to update and navigate.
|
||||
|
||||
In the context of React, modular documentation should cover both React components and the overall architecture of the app.
|
||||
|
||||
### Split Documentation into Smaller Sections
|
||||
|
||||
When documenting, break down each part of your codebase into its smallest logical unit:
|
||||
|
||||
1. **Functions**: Explain its purpose, inputs (arguments), and outputs (returns).
|
||||
2. **Components**: Detail props, their types, default values, and the UI they render.
|
||||
3. **Utilities/Helpers**: Document what a utility does, its inputs, and outputs.
|
||||
4. **APIs**: Cover the endpoint, request format, and expected response.
|
||||
|
||||
Each section should have a consistent structure for easy understanding and reference.
|
||||
|
||||
## Documenting Functions
|
||||
|
||||
#### Structure for Documenting Functions
|
||||
|
||||
1. **Function Name**: A short and descriptive name.
|
||||
2. **Purpose**: Briefly explain what the function does.
|
||||
3. **Inputs**:
|
||||
- List each parameter.
|
||||
- Include types and default values if applicable.
|
||||
4. **Output**: Describe the return value and its type.
|
||||
5. **Code Examples**: Provide a usage example.
|
||||
|
||||
#### Example: Utility Function Documentation
|
||||
|
||||
````markdown
|
||||
## `formatDate`
|
||||
|
||||
### Purpose
|
||||
|
||||
The `formatDate` function converts a `Date` object into a human-readable string format.
|
||||
|
||||
### Inputs
|
||||
|
||||
| Parameter | Type | Default Value | Description |
|
||||
| --------- | -------- | ------------- | -------------------------- |
|
||||
| `date` | `Date` | - | The date object to format. |
|
||||
| `locale` | `string` | `"en-US"` | The locale for formatting. |
|
||||
|
||||
### Output
|
||||
|
||||
Returns a `string` representing the formatted date.
|
||||
|
||||
### Example
|
||||
|
||||
```javascript
|
||||
import { formatDate } from "./utils";
|
||||
|
||||
const date = new Date("2024-11-21");
|
||||
console.log(formatDate(date)); // Output: "November 21, 2024"
|
||||
```
|
||||
````
|
||||
|
||||
## Documenting Components
|
||||
|
||||
#### Structure for Documenting Components
|
||||
|
||||
1. **Component Name**: Name of the React component.
|
||||
2. **Purpose**: Explain what the component is for and its use case.
|
||||
3. **Props**:
|
||||
- List each prop.
|
||||
- Include type, whether it’s required, and default value.
|
||||
4. **Behavior**: Describe what the component does and any side effects.
|
||||
5. **Output**: Explain what the component renders or returns.
|
||||
6. **Code Examples**: Show how to use the component.
|
||||
|
||||
#### Example: Component Documentation
|
||||
|
||||
`````markdown
|
||||
## `Button`
|
||||
|
||||
### Purpose
|
||||
|
||||
The `Button` component renders a clickable button with customizable styles and behavior.
|
||||
|
||||
### Props
|
||||
|
||||
| Prop Name | Type | Required | Default Value | Description |
|
||||
| ---------- | ---------- | -------- | ------------- | ---------------------------------- |
|
||||
| `label` | `string` | Yes | - | The text displayed on the button. |
|
||||
| `onClick` | `function` | No | `() => {}` | The function to call when clicked. |
|
||||
| `disabled` | `boolean` | No | `false` | Disables the button if true. |
|
||||
| `style` | `object` | No | `{}` | Inline styles for the button. |
|
||||
|
||||
### Behavior
|
||||
|
||||
- If `disabled` is true, the button cannot be clicked.
|
||||
- The `onClick` function will only be called when the button is enabled.
|
||||
|
||||
### Output
|
||||
|
||||
Renders a `<button>` element styled based on the passed props.
|
||||
|
||||
### Example
|
||||
|
||||
```jsx
|
||||
import Button from "./components/Button";
|
||||
|
||||
<Button
|
||||
label="Submit"
|
||||
onClick={() => console.log("Clicked")}
|
||||
disabled={false}
|
||||
style={{ color: "white", backgroundColor: "blue" }}
|
||||
/>;
|
||||
```
|
||||
|
||||
## Documenting Advanced Components
|
||||
|
||||
For components with complex functionality (e.g., controlled components, form components, or components interacting with APIs), follow these additional guidelines:
|
||||
|
||||
1. **State Management**: Document the state it manages and how to interact with it.
|
||||
2. **Lifecycle**: If it uses React lifecycle methods or hooks, explain their purpose.
|
||||
3. **Events**: Document events (e.g., `onChange`, `onBlur`) and their payloads.
|
||||
|
||||
#### Example: Complex Component Documentation
|
||||
|
||||
````markdown
|
||||
## `SearchBar`
|
||||
|
||||
### Purpose
|
||||
|
||||
The `SearchBar` component provides a text input for users to search and emits search queries through the `onSearch` event.
|
||||
|
||||
### Props
|
||||
|
||||
| Prop Name | Type | Required | Default Value | Description |
|
||||
| ------------- | ---------- | -------- | ------------- | -------------------------------------------- |
|
||||
| `placeholder` | `string` | No | `"Search..."` | The placeholder text for the input. |
|
||||
| `onSearch` | `function` | Yes | - | Callback fired when the search is submitted. |
|
||||
| `debounce` | `number` | No | `300` | Time in milliseconds to debounce user input. |
|
||||
|
||||
### Behavior
|
||||
|
||||
1. The component manages the `inputValue` state.
|
||||
2. After the user stops typing for the `debounce` duration, it triggers the `onSearch` callback with the input value.
|
||||
|
||||
### Output
|
||||
|
||||
Renders:
|
||||
|
||||
- A `<div>` wrapper.
|
||||
- An `<input>` field styled with default or custom styles.
|
||||
|
||||
### Example
|
||||
|
||||
```jsx
|
||||
import SearchBar from "./components/SearchBar";
|
||||
|
||||
<SearchBar
|
||||
placeholder="Search items..."
|
||||
onSearch={(query) => console.log(query)}
|
||||
debounce={500}
|
||||
/>;
|
||||
```
|
||||
````
|
||||
`````
|
||||
|
||||
## Documenting Custom Hooks
|
||||
|
||||
#### Structure for Documenting Hooks
|
||||
|
||||
1. **Hook Name**: Name of the hook.
|
||||
2. **Purpose**: Why it’s used and what it does.
|
||||
3. **Inputs**: Parameters passed to the hook.
|
||||
4. **State/Outputs**: State or values returned by the hook.
|
||||
5. **Usage**: Show an example.
|
||||
|
||||
#### Example: Hook Documentation
|
||||
|
||||
````markdown
|
||||
## `useFetch`
|
||||
|
||||
### Purpose
|
||||
|
||||
The `useFetch` hook manages data fetching and provides loading, error, and response states.
|
||||
|
||||
### Inputs
|
||||
|
||||
| Parameter | Type | Required | Default Value | Description |
|
||||
| --------- | -------- | -------- | ------------- | ------------------------------------- |
|
||||
| `url` | `string` | Yes | - | The endpoint to fetch data from. |
|
||||
| `options` | `object` | No | `{}` | Fetch options like headers or method. |
|
||||
|
||||
### State/Outputs
|
||||
|
||||
| State Name | Type | Description |
|
||||
| ---------- | --------- | ---------------------------------------- |
|
||||
| `data` | `any` | The response data from the API. |
|
||||
| `loading` | `boolean` | Indicates if the request is in progress. |
|
||||
| `error` | `object` | The error object if the request fails. |
|
||||
|
||||
### Example
|
||||
|
||||
```javascript
|
||||
import { useFetch } from "./hooks/useFetch";
|
||||
|
||||
const MyComponent = () => {
|
||||
const { data, loading, error } = useFetch("/api/data");
|
||||
|
||||
if (loading) return <p>Loading...</p>;
|
||||
if (error) return <p>Error: {error.message}</p>;
|
||||
|
||||
return <div>{JSON.stringify(data)}</div>;
|
||||
};
|
||||
```
|
||||
````
|
||||
|
||||
#### General Best Practices for Modular Documentation
|
||||
|
||||
1. **Be Consistent**: Use the same format for all functions, components, and hooks.
|
||||
2. **Focus on Inputs and Outputs**: Developers care about what they give and what they get.
|
||||
3. **Use Examples**: Code examples make documentation actionable.
|
||||
4. **Avoid Overloading**: Document each function/component in its own file if it’s complex.
|
||||
5. **Explain Behavior**: Describe any side effects, state changes, or interactions.
|
||||
|
||||
---
|
||||
|
||||
## Guide to Managing Files and Folders for Documentation in Docsify
|
||||
|
||||
Docsify is a flexible tool for generating documentation from markdown files. To keep your Docsify-based project well-organized, here’s a guide on how to manage files and folders effectively.
|
||||
|
||||
#### 1. Project Structure Overview
|
||||
|
||||
A clean folder structure is crucial for scalability, readability, and ease of maintenance. Below is a suggested structure:
|
||||
|
||||
```
|
||||
/docs # Root folder for your Docsify project
|
||||
│
|
||||
├── /assets/ # Static resources (images, videos, files, etc.)
|
||||
│ └── /images/ # Folder for images used in your docs
|
||||
│ └── gitWorkFlow.svg # Example image used in documentation
|
||||
│ └── /videos/ # Folder for tutorial videos or related media
|
||||
│ └── walkthrough.mp4 # old walkthrough presentation
|
||||
│
|
||||
├── /documents/ # Folder for the main documentation content
|
||||
│ └── docsifyGuide.md # Documentation for setting up and using Docsify
|
||||
│ └── projectStructure.md # Explanation of the project structure and organization
|
||||
│ └── /components/ # Folder for documentation on different components
|
||||
│ └── input.md # Input component documentation
|
||||
│ └── others.md # Other components documentation
|
||||
│ └── etc.md # Any additional miscellaneous documentation
|
||||
│
|
||||
├── /style/ # Folder for custom styles
|
||||
│ └── style.css # Custom CSS file for styling the documentation
|
||||
│
|
||||
└── index.html # Main entry point for Docsify (loads the documentation)
|
||||
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### 2. Folder Breakdown
|
||||
|
||||
- **/docs**: This is the core folder that contains all of your Markdown (`.md`) files. You can organize these files by sections or topics such as guides, API references, and tutorials. Subfolders like `/guide`, `/reference`, or `/tutorials` help keep related files together.
|
||||
|
||||
- **/assets**: This folder is for any images, diagrams, videos, or other static files referenced in your documentation. It's best to organize your media into subfolders based on the section they belong to.
|
||||
|
||||
Example:
|
||||
|
||||
```
|
||||
/assets
|
||||
├── /images/
|
||||
│ ├── diagram1.png
|
||||
│ └── screenshot.png
|
||||
└── /videos/
|
||||
└── tutorial.mp4
|
||||
```
|
||||
|
||||
- **/lib**: If you want to add custom JavaScript or other scripts to enhance Docsify’s functionality, place them in the `/lib` folder. This can include themes, custom navigation, or interactive features.
|
||||
|
||||
---
|
||||
|
||||
#### 3. Naming Conventions
|
||||
|
||||
Use simple, descriptive names for your files and folders. Avoid spaces in filenames—use hyphens (`-`) instead. For example:
|
||||
|
||||
- **Correct**: `installation-guide.md`, `api-reference.md`
|
||||
- **Incorrect**: `installation guide.md`, `api reference.md`
|
||||
|
||||
This keeps URLs and links consistent and easier to handle.
|
||||
|
||||
---
|
||||
|
||||
#### 4. Organizing Documentation Files
|
||||
|
||||
1. **Homepage (index.md)**:
|
||||
The `index.md` file serves as the homepage for your documentation. It should provide an introduction to the project and link to other sections of your documentation.
|
||||
|
||||
2. **Guide Section**:
|
||||
Place introductory content, installation instructions, and tutorials in the `/guide` folder. Each file should be named based on its content (e.g., `installation.md`, `getting-started.md`).
|
||||
|
||||
3. **Reference Section**:
|
||||
For API documentation or technical references, create a `/reference` folder. You might have files like `api-reference.md`, `config.md`, or `troubleshooting.md`.
|
||||
|
||||
4. **Assets**:
|
||||
Organize images, videos, or diagrams in the `/assets` folder. Keep this structure consistent across sections (e.g., `/assets/images/`, `/assets/videos/`).
|
||||
|
||||
---
|
||||
|
||||
#### 5. Writing the \_sidebar.md File
|
||||
|
||||
The `_sidebar.md` file controls the sidebar navigation in Docsify. It defines the links that appear on the sidebar, which can point to sections of the documentation or external URLs.
|
||||
|
||||
Here’s how to structure the `_sidebar.md` file:
|
||||
|
||||
- **Basic Structure**:
|
||||
You can list the sections of your documentation as clickable links. Use Markdown syntax to link to the different `.md` files within your `/docs` folder.
|
||||
|
||||
Example:
|
||||
|
||||
```markdown
|
||||
- [Home](/) # Link to the homepage
|
||||
- [Getting Started](/guide/intro.md) # Link to the "Getting Started" guide
|
||||
- [API Reference](/reference/api.md) # Link to the API documentation
|
||||
- [Installation](/guide/installation.md) # Link to the installation guide
|
||||
```
|
||||
|
||||
- **Nested Links**:
|
||||
To organize sections into subcategories, you can nest links under headings. This helps create a hierarchical structure in the sidebar.
|
||||
|
||||
Example:
|
||||
|
||||
```markdown
|
||||
- [Home](/)
|
||||
- [Guide](/guide/intro.md)
|
||||
- [Introduction](/guide/intro.md)
|
||||
- [Usage](/guide/usage.md)
|
||||
- [API Reference](/reference/api.md)
|
||||
- [Authentication](/reference/authentication.md)
|
||||
- [Endpoints](/reference/endpoints.md)
|
||||
```
|
||||
|
||||
- **External Links**:
|
||||
You can also add external links to resources outside of your Docsify project.
|
||||
|
||||
Example:
|
||||
|
||||
```markdown
|
||||
- [External Resource](https://example.com)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### 6. Managing Large Projects
|
||||
|
||||
For large documentation projects, it’s best to break content into smaller, manageable sections. This will help keep the documentation organized and make it easier for team members to collaborate.
|
||||
|
||||
- **Modular Sections**:
|
||||
Use subfolders like `/getting-started/`, `/setup/`, and `/troubleshooting/` to logically divide your content. Each section should have its own introduction, details, and examples.
|
||||
|
||||
- **Indexing**:
|
||||
Consider creating an `index.md` in each major folder (e.g., `/guide/index.md`, `/reference/index.md`) for clarity and easy navigation.
|
||||
|
||||
---
|
||||
# Why Documentation is Important for Developers
|
||||
|
||||
Documentation helps developers work efficiently and ensures projects run smoothly. Here’s why it’s crucial:
|
||||
|
||||
1. **Knowledge Sharing**: It helps team members understand the system and communicate effectively, especially when new people join the project.
|
||||
|
||||
2. **Code Maintenance**: Good documentation makes it easier to fix bugs, make updates, and keep the code consistent.
|
||||
|
||||
3. **Project Longevity**: It ensures that project knowledge is preserved, so future developers can maintain or improve the code without confusion.
|
||||
|
||||
4. **Troubleshooting**: Developers can quickly find solutions to problems or understand past decisions, saving time and reducing errors.
|
||||
|
||||
5. **Testing and Quality**: Documentation helps ensure the right testing processes are followed, leading to better-quality code.
|
||||
|
||||
6. **Efficiency**: It saves time by reducing the need to explain things repeatedly or search for answers.
|
||||
|
||||
In short, internal documentation keeps projects organized, helps teams collaborate, and ensures the software can be maintained and improved over time.
|
||||
|
||||
---
|
||||
|
||||
## Guide to Writing Modular Documentation for React Projects
|
||||
|
||||
Modular documentation refers to organizing your documentation into independent, reusable sections or modules. Each module typically covers a specific part of your project, making it easier to update and navigate.
|
||||
|
||||
In the context of React, modular documentation should cover both React components and the overall architecture of the app.
|
||||
|
||||
### Split Documentation into Smaller Sections
|
||||
|
||||
When documenting, break down each part of your codebase into its smallest logical unit:
|
||||
|
||||
1. **Functions**: Explain its purpose, inputs (arguments), and outputs (returns).
|
||||
2. **Components**: Detail props, their types, default values, and the UI they render.
|
||||
3. **Utilities/Helpers**: Document what a utility does, its inputs, and outputs.
|
||||
4. **APIs**: Cover the endpoint, request format, and expected response.
|
||||
|
||||
Each section should have a consistent structure for easy understanding and reference.
|
||||
|
||||
## Documenting Functions
|
||||
|
||||
#### Structure for Documenting Functions
|
||||
|
||||
1. **Function Name**: A short and descriptive name.
|
||||
2. **Purpose**: Briefly explain what the function does.
|
||||
3. **Inputs**:
|
||||
- List each parameter.
|
||||
- Include types and default values if applicable.
|
||||
4. **Output**: Describe the return value and its type.
|
||||
5. **Code Examples**: Provide a usage example.
|
||||
|
||||
#### Example: Utility Function Documentation
|
||||
|
||||
````markdown
|
||||
## `formatDate`
|
||||
|
||||
### Purpose
|
||||
|
||||
The `formatDate` function converts a `Date` object into a human-readable string format.
|
||||
|
||||
### Inputs
|
||||
|
||||
| Parameter | Type | Default Value | Description |
|
||||
| --------- | -------- | ------------- | -------------------------- |
|
||||
| `date` | `Date` | - | The date object to format. |
|
||||
| `locale` | `string` | `"en-US"` | The locale for formatting. |
|
||||
|
||||
### Output
|
||||
|
||||
Returns a `string` representing the formatted date.
|
||||
|
||||
### Example
|
||||
|
||||
```javascript
|
||||
import { formatDate } from "./utils";
|
||||
|
||||
const date = new Date("2024-11-21");
|
||||
console.log(formatDate(date)); // Output: "November 21, 2024"
|
||||
```
|
||||
````
|
||||
|
||||
## Documenting Components
|
||||
|
||||
#### Structure for Documenting Components
|
||||
|
||||
1. **Component Name**: Name of the React component.
|
||||
2. **Purpose**: Explain what the component is for and its use case.
|
||||
3. **Props**:
|
||||
- List each prop.
|
||||
- Include type, whether it’s required, and default value.
|
||||
4. **Behavior**: Describe what the component does and any side effects.
|
||||
5. **Output**: Explain what the component renders or returns.
|
||||
6. **Code Examples**: Show how to use the component.
|
||||
|
||||
#### Example: Component Documentation
|
||||
|
||||
`````markdown
|
||||
## `Button`
|
||||
|
||||
### Purpose
|
||||
|
||||
The `Button` component renders a clickable button with customizable styles and behavior.
|
||||
|
||||
### Props
|
||||
|
||||
| Prop Name | Type | Required | Default Value | Description |
|
||||
| ---------- | ---------- | -------- | ------------- | ---------------------------------- |
|
||||
| `label` | `string` | Yes | - | The text displayed on the button. |
|
||||
| `onClick` | `function` | No | `() => {}` | The function to call when clicked. |
|
||||
| `disabled` | `boolean` | No | `false` | Disables the button if true. |
|
||||
| `style` | `object` | No | `{}` | Inline styles for the button. |
|
||||
|
||||
### Behavior
|
||||
|
||||
- If `disabled` is true, the button cannot be clicked.
|
||||
- The `onClick` function will only be called when the button is enabled.
|
||||
|
||||
### Output
|
||||
|
||||
Renders a `<button>` element styled based on the passed props.
|
||||
|
||||
### Example
|
||||
|
||||
```jsx
|
||||
import Button from "./components/Button";
|
||||
|
||||
<Button
|
||||
label="Submit"
|
||||
onClick={() => console.log("Clicked")}
|
||||
disabled={false}
|
||||
style={{ color: "white", backgroundColor: "blue" }}
|
||||
/>;
|
||||
```
|
||||
|
||||
## Documenting Advanced Components
|
||||
|
||||
For components with complex functionality (e.g., controlled components, form components, or components interacting with APIs), follow these additional guidelines:
|
||||
|
||||
1. **State Management**: Document the state it manages and how to interact with it.
|
||||
2. **Lifecycle**: If it uses React lifecycle methods or hooks, explain their purpose.
|
||||
3. **Events**: Document events (e.g., `onChange`, `onBlur`) and their payloads.
|
||||
|
||||
#### Example: Complex Component Documentation
|
||||
|
||||
````markdown
|
||||
## `SearchBar`
|
||||
|
||||
### Purpose
|
||||
|
||||
The `SearchBar` component provides a text input for users to search and emits search queries through the `onSearch` event.
|
||||
|
||||
### Props
|
||||
|
||||
| Prop Name | Type | Required | Default Value | Description |
|
||||
| ------------- | ---------- | -------- | ------------- | -------------------------------------------- |
|
||||
| `placeholder` | `string` | No | `"Search..."` | The placeholder text for the input. |
|
||||
| `onSearch` | `function` | Yes | - | Callback fired when the search is submitted. |
|
||||
| `debounce` | `number` | No | `300` | Time in milliseconds to debounce user input. |
|
||||
|
||||
### Behavior
|
||||
|
||||
1. The component manages the `inputValue` state.
|
||||
2. After the user stops typing for the `debounce` duration, it triggers the `onSearch` callback with the input value.
|
||||
|
||||
### Output
|
||||
|
||||
Renders:
|
||||
|
||||
- A `<div>` wrapper.
|
||||
- An `<input>` field styled with default or custom styles.
|
||||
|
||||
### Example
|
||||
|
||||
```jsx
|
||||
import SearchBar from "./components/SearchBar";
|
||||
|
||||
<SearchBar
|
||||
placeholder="Search items..."
|
||||
onSearch={(query) => console.log(query)}
|
||||
debounce={500}
|
||||
/>;
|
||||
```
|
||||
````
|
||||
`````
|
||||
|
||||
## Documenting Custom Hooks
|
||||
|
||||
#### Structure for Documenting Hooks
|
||||
|
||||
1. **Hook Name**: Name of the hook.
|
||||
2. **Purpose**: Why it’s used and what it does.
|
||||
3. **Inputs**: Parameters passed to the hook.
|
||||
4. **State/Outputs**: State or values returned by the hook.
|
||||
5. **Usage**: Show an example.
|
||||
|
||||
#### Example: Hook Documentation
|
||||
|
||||
````markdown
|
||||
## `useFetch`
|
||||
|
||||
### Purpose
|
||||
|
||||
The `useFetch` hook manages data fetching and provides loading, error, and response states.
|
||||
|
||||
### Inputs
|
||||
|
||||
| Parameter | Type | Required | Default Value | Description |
|
||||
| --------- | -------- | -------- | ------------- | ------------------------------------- |
|
||||
| `url` | `string` | Yes | - | The endpoint to fetch data from. |
|
||||
| `options` | `object` | No | `{}` | Fetch options like headers or method. |
|
||||
|
||||
### State/Outputs
|
||||
|
||||
| State Name | Type | Description |
|
||||
| ---------- | --------- | ---------------------------------------- |
|
||||
| `data` | `any` | The response data from the API. |
|
||||
| `loading` | `boolean` | Indicates if the request is in progress. |
|
||||
| `error` | `object` | The error object if the request fails. |
|
||||
|
||||
### Example
|
||||
|
||||
```javascript
|
||||
import { useFetch } from "./hooks/useFetch";
|
||||
|
||||
const MyComponent = () => {
|
||||
const { data, loading, error } = useFetch("/api/data");
|
||||
|
||||
if (loading) return <p>Loading...</p>;
|
||||
if (error) return <p>Error: {error.message}</p>;
|
||||
|
||||
return <div>{JSON.stringify(data)}</div>;
|
||||
};
|
||||
```
|
||||
````
|
||||
|
||||
#### General Best Practices for Modular Documentation
|
||||
|
||||
1. **Be Consistent**: Use the same format for all functions, components, and hooks.
|
||||
2. **Focus on Inputs and Outputs**: Developers care about what they give and what they get.
|
||||
3. **Use Examples**: Code examples make documentation actionable.
|
||||
4. **Avoid Overloading**: Document each function/component in its own file if it’s complex.
|
||||
5. **Explain Behavior**: Describe any side effects, state changes, or interactions.
|
||||
|
||||
---
|
||||
|
||||
## Guide to Managing Files and Folders for Documentation in Docsify
|
||||
|
||||
Docsify is a flexible tool for generating documentation from markdown files. To keep your Docsify-based project well-organized, here’s a guide on how to manage files and folders effectively.
|
||||
|
||||
#### 1. Project Structure Overview
|
||||
|
||||
A clean folder structure is crucial for scalability, readability, and ease of maintenance. Below is a suggested structure:
|
||||
|
||||
```
|
||||
/docs # Root folder for your Docsify project
|
||||
│
|
||||
├── /assets/ # Static resources (images, videos, files, etc.)
|
||||
│ └── /images/ # Folder for images used in your docs
|
||||
│ └── gitWorkFlow.svg # Example image used in documentation
|
||||
│ └── /videos/ # Folder for tutorial videos or related media
|
||||
│ └── walkthrough.mp4 # old walkthrough presentation
|
||||
│
|
||||
├── /documents/ # Folder for the main documentation content
|
||||
│ └── docsifyGuide.md # Documentation for setting up and using Docsify
|
||||
│ └── projectStructure.md # Explanation of the project structure and organization
|
||||
│ └── /components/ # Folder for documentation on different components
|
||||
│ └── input.md # Input component documentation
|
||||
│ └── others.md # Other components documentation
|
||||
│ └── etc.md # Any additional miscellaneous documentation
|
||||
│
|
||||
├── /style/ # Folder for custom styles
|
||||
│ └── style.css # Custom CSS file for styling the documentation
|
||||
│
|
||||
└── index.html # Main entry point for Docsify (loads the documentation)
|
||||
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### 2. Folder Breakdown
|
||||
|
||||
- **/docs**: This is the core folder that contains all of your Markdown (`.md`) files. You can organize these files by sections or topics such as guides, API references, and tutorials. Subfolders like `/guide`, `/reference`, or `/tutorials` help keep related files together.
|
||||
|
||||
- **/assets**: This folder is for any images, diagrams, videos, or other static files referenced in your documentation. It's best to organize your media into subfolders based on the section they belong to.
|
||||
|
||||
Example:
|
||||
|
||||
```
|
||||
/assets
|
||||
├── /images/
|
||||
│ ├── diagram1.png
|
||||
│ └── screenshot.png
|
||||
└── /videos/
|
||||
└── tutorial.mp4
|
||||
```
|
||||
|
||||
- **/lib**: If you want to add custom JavaScript or other scripts to enhance Docsify’s functionality, place them in the `/lib` folder. This can include themes, custom navigation, or interactive features.
|
||||
|
||||
---
|
||||
|
||||
#### 3. Naming Conventions
|
||||
|
||||
Use simple, descriptive names for your files and folders. Avoid spaces in filenames—use hyphens (`-`) instead. For example:
|
||||
|
||||
- **Correct**: `installation-guide.md`, `api-reference.md`
|
||||
- **Incorrect**: `installation guide.md`, `api reference.md`
|
||||
|
||||
This keeps URLs and links consistent and easier to handle.
|
||||
|
||||
---
|
||||
|
||||
#### 4. Organizing Documentation Files
|
||||
|
||||
1. **Homepage (index.md)**:
|
||||
The `index.md` file serves as the homepage for your documentation. It should provide an introduction to the project and link to other sections of your documentation.
|
||||
|
||||
2. **Guide Section**:
|
||||
Place introductory content, installation instructions, and tutorials in the `/guide` folder. Each file should be named based on its content (e.g., `installation.md`, `getting-started.md`).
|
||||
|
||||
3. **Reference Section**:
|
||||
For API documentation or technical references, create a `/reference` folder. You might have files like `api-reference.md`, `config.md`, or `troubleshooting.md`.
|
||||
|
||||
4. **Assets**:
|
||||
Organize images, videos, or diagrams in the `/assets` folder. Keep this structure consistent across sections (e.g., `/assets/images/`, `/assets/videos/`).
|
||||
|
||||
---
|
||||
|
||||
#### 5. Writing the \_sidebar.md File
|
||||
|
||||
The `_sidebar.md` file controls the sidebar navigation in Docsify. It defines the links that appear on the sidebar, which can point to sections of the documentation or external URLs.
|
||||
|
||||
Here’s how to structure the `_sidebar.md` file:
|
||||
|
||||
- **Basic Structure**:
|
||||
You can list the sections of your documentation as clickable links. Use Markdown syntax to link to the different `.md` files within your `/docs` folder.
|
||||
|
||||
Example:
|
||||
|
||||
```markdown
|
||||
- [Home](/) # Link to the homepage
|
||||
- [Getting Started](/guide/intro.md) # Link to the "Getting Started" guide
|
||||
- [API Reference](/reference/api.md) # Link to the API documentation
|
||||
- [Installation](/guide/installation.md) # Link to the installation guide
|
||||
```
|
||||
|
||||
- **Nested Links**:
|
||||
To organize sections into subcategories, you can nest links under headings. This helps create a hierarchical structure in the sidebar.
|
||||
|
||||
Example:
|
||||
|
||||
```markdown
|
||||
- [Home](/)
|
||||
- [Guide](/guide/intro.md)
|
||||
- [Introduction](/guide/intro.md)
|
||||
- [Usage](/guide/usage.md)
|
||||
- [API Reference](/reference/api.md)
|
||||
- [Authentication](/reference/authentication.md)
|
||||
- [Endpoints](/reference/endpoints.md)
|
||||
```
|
||||
|
||||
- **External Links**:
|
||||
You can also add external links to resources outside of your Docsify project.
|
||||
|
||||
Example:
|
||||
|
||||
```markdown
|
||||
- [External Resource](https://example.com)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### 6. Managing Large Projects
|
||||
|
||||
For large documentation projects, it’s best to break content into smaller, manageable sections. This will help keep the documentation organized and make it easier for team members to collaborate.
|
||||
|
||||
- **Modular Sections**:
|
||||
Use subfolders like `/getting-started/`, `/setup/`, and `/troubleshooting/` to logically divide your content. Each section should have its own introduction, details, and examples.
|
||||
|
||||
- **Indexing**:
|
||||
Consider creating an `index.md` in each major folder (e.g., `/guide/index.md`, `/reference/index.md`) for clarity and easy navigation.
|
||||
|
||||
---
|
||||
|
||||
@@ -1,338 +1,338 @@
|
||||
## Git basic Workflow
|
||||
|
||||
<p align="center">
|
||||
<img src="../assets/images/gitWorkFlow.svg" alt="Git basic Workflow">
|
||||
<p align="center">Fig.1 - Git basic Workflow</p>
|
||||
</p>
|
||||
|
||||
---
|
||||
|
||||
## Git Commands
|
||||
|
||||
1. Initialize git:
|
||||
|
||||
```bash
|
||||
git init
|
||||
```
|
||||
|
||||
2. Check remote repository:
|
||||
|
||||
```bash
|
||||
git remote
|
||||
git remote -v
|
||||
```
|
||||
|
||||
3. Connect to remote repository (optional):
|
||||
If remote repository is not connected use,
|
||||
|
||||
```bash
|
||||
git remote add origin http://185.100.212.76:7776/Vishnu/modeling_app.git
|
||||
```
|
||||
|
||||
To change url use,
|
||||
|
||||
```bash
|
||||
git remote set-url origin http://185.100.212.76:7776/Vishnu/modeling_app.git
|
||||
```
|
||||
|
||||
4. Fetch from origin:
|
||||
|
||||
On fetch mention --all to pull all branches or mention remote_name and branch_name to fetch a particular branch.
|
||||
Use --force to perform force Pull.
|
||||
|
||||
```bash
|
||||
git fetch
|
||||
git pull
|
||||
```
|
||||
|
||||
5. Staging and Commit changes:
|
||||
|
||||
- "." indicated all changed files will be staged.
|
||||
- For, specific file replace "." with the path to the specific file or directory(folder).
|
||||
- "commit message" - replace text in this phrase to your commit discription.
|
||||
|
||||
**Staging**
|
||||
|
||||
```bash
|
||||
git add .
|
||||
git add path/to/directory_or_file
|
||||
```
|
||||
|
||||
To Unstage all files that were added to the staging area but does not affect the working directory:
|
||||
|
||||
```bash
|
||||
git reset
|
||||
```
|
||||
|
||||
**Commit**
|
||||
|
||||
```bash
|
||||
git commit -m "commit message"
|
||||
```
|
||||
|
||||
Creates a new commit that undoes the changes introduced by the specified commit, use --hard for discarding all changes since that commit (Warning: This command can permanently delete data.):
|
||||
|
||||
```bash
|
||||
git revert commit_hash
|
||||
```
|
||||
|
||||
6. Inspecting Commits:
|
||||
|
||||
- View the commit history:
|
||||
|
||||
```bash
|
||||
git log
|
||||
```
|
||||
|
||||
- View a graph of commits:
|
||||
|
||||
```bash
|
||||
git log --graph --oneline --all
|
||||
```
|
||||
|
||||
7. Push to remote repository:
|
||||
|
||||
- If the branch is creted for the first time and dose not located in the remote repsitory. Use,
|
||||
|
||||
```bash
|
||||
git push --set-upstream origin new_branch_name
|
||||
```
|
||||
|
||||
- Normal push
|
||||
|
||||
```bash
|
||||
git push origin new_branch_name
|
||||
```
|
||||
|
||||
- Force push and Safer force push
|
||||
|
||||
```bash
|
||||
git push --force
|
||||
git push --force-with-lease
|
||||
```
|
||||
|
||||
- Delete branch in remote repository
|
||||
|
||||
```bash
|
||||
git push origin --delete branch_name
|
||||
```
|
||||
|
||||
8. Creating and Switching Branches:
|
||||
|
||||
- To check current branch name. Use,
|
||||
|
||||
```bash
|
||||
git remote
|
||||
```
|
||||
|
||||
- To checkout from current branch and move to another branch. Use,
|
||||
|
||||
```bash
|
||||
git checkout branch_name
|
||||
```
|
||||
|
||||
- To checkout from current branch and create new branch. Use,
|
||||
|
||||
```bash
|
||||
git checkout -b new_branch_name
|
||||
```
|
||||
|
||||
9. Merging branches:
|
||||
|
||||
- Merges the specified branch to the current active branch:
|
||||
|
||||
```bash
|
||||
git merge branch_name
|
||||
```
|
||||
|
||||
- This will only perform the merge if it can be fast-forwarded. If a fast-forward isn't possible, Git will abort the merge:
|
||||
|
||||
```bash
|
||||
git merge --ff-only branch_name
|
||||
```
|
||||
|
||||
- Provide a custom merge commit message:
|
||||
|
||||
```bash
|
||||
git merge -m "Custom merge message" branch_name
|
||||
```
|
||||
|
||||
- Merge without committing automatically:
|
||||
|
||||
```bash
|
||||
git merge --no-commit branch_name
|
||||
```
|
||||
|
||||
- Continue the merge after resolving conflicts:
|
||||
|
||||
```bash
|
||||
git merge --continue
|
||||
```
|
||||
|
||||
- Abort a merge in progress:
|
||||
|
||||
```bash
|
||||
git merge --abort
|
||||
```
|
||||
|
||||
10. Stash changes:
|
||||
|
||||
- Stashes changes made in current branch:
|
||||
|
||||
```bash
|
||||
git stash
|
||||
```
|
||||
|
||||
- Stashes changes made in current branch with message:
|
||||
|
||||
```bash
|
||||
git stash save "message"
|
||||
```
|
||||
|
||||
- Apply Latest stash and apply and remove form stash list:
|
||||
|
||||
```bash
|
||||
git stash apply
|
||||
git stash pop
|
||||
```
|
||||
|
||||
- View Stash:
|
||||
|
||||
```bash
|
||||
git stash list
|
||||
```
|
||||
|
||||
- Clear all stashes:
|
||||
|
||||
```bash
|
||||
git stash clear
|
||||
```
|
||||
|
||||
- Remove the latest stash:
|
||||
|
||||
```bash
|
||||
git stash drop
|
||||
```
|
||||
|
||||
11. Branch commands:
|
||||
|
||||
- To View all local and remote branches
|
||||
|
||||
```bash
|
||||
git branch
|
||||
git branch -a
|
||||
```
|
||||
|
||||
- To Create branch
|
||||
|
||||
```bash
|
||||
git branch branch_name
|
||||
```
|
||||
|
||||
- To switch between branch
|
||||
|
||||
```bash
|
||||
git checkout branch_name
|
||||
```
|
||||
|
||||
Alternatively if you want to bring the changes to the new branch,
|
||||
|
||||
```bash
|
||||
git switch branch_name
|
||||
```
|
||||
|
||||
- To switch between branch
|
||||
|
||||
```bash
|
||||
git checkout -b branch_name
|
||||
```
|
||||
|
||||
Alternatively,
|
||||
|
||||
```bash
|
||||
git switch -c branch_name
|
||||
```
|
||||
|
||||
- To rename branch
|
||||
|
||||
```bash
|
||||
git branch -m old_branch_name new_branch_name
|
||||
```
|
||||
|
||||
- To Delete branch (use -D to force delete)
|
||||
|
||||
```bash
|
||||
git branch -d branch_name
|
||||
```
|
||||
|
||||
12. Other git comments consiered usefull in previous encounters:
|
||||
|
||||
- press q to exit git response
|
||||
|
||||
- Prevent git from creating **zoneIdentifier** files
|
||||
|
||||
```bash
|
||||
git config core.protectNTFS false
|
||||
```
|
||||
---
|
||||
|
||||
## Git Branching
|
||||
|
||||
<p align="center">
|
||||
<img src="../assets/images/gitBranching.svg" alt="Git Branching">
|
||||
<p align="center">Fig.2 - Git Branching</p>
|
||||
</p>
|
||||
|
||||
This diagram represents a branching model for managing the development of a software project. It uses different branches to organize and control how code changes are developed, tested, and released. Here’s a breakdown of the key concepts, simplified for someone new:
|
||||
|
||||
### **Main Components**
|
||||
1. **Main Branch** (blue line):
|
||||
- This branch represents the "production" or "live" version of the project.
|
||||
- Only stable and tested versions of the code are added here.
|
||||
- Releases like `v0.1`, `v0.2`, and `v1.0` are tagged here.
|
||||
|
||||
2. **Develop Branch** (purple line):
|
||||
- This is where active development happens.
|
||||
- Features or fixes are integrated here before they are prepared for a release.
|
||||
- It acts as a staging area for new work to ensure it’s functional and complete.
|
||||
|
||||
3. **Feature Branches** (green lines):
|
||||
- These branches are used to develop specific features or tasks.
|
||||
- Developers create a new branch for each feature and work on it independently.
|
||||
- Once complete, they are merged into the **Develop Branch**.
|
||||
|
||||
4. **Release Branch** (teal line):
|
||||
- Before a release is finalized, a release branch is created.
|
||||
- Final fixes and testing are done here to ensure stability.
|
||||
- Once complete, it is merged into both **Main** and **Develop** branches to mark the release.
|
||||
|
||||
5. **Hotfix Branch** (red line):
|
||||
- This branch is for urgent fixes to the live code.
|
||||
- If an issue is found in the **Main Branch** (e.g., a bug in `v0.1`), a **Hotfix Branch** is created.
|
||||
- After fixing, it is merged into both **Main** and **Develop** to ensure the fix is applied everywhere.
|
||||
|
||||
### **Workflow Summary**
|
||||
1. **Start a Feature:**
|
||||
- Create a feature branch from the **Develop Branch**.
|
||||
- Work on your task and complete it.
|
||||
|
||||
2. **Integrate Your Work:**
|
||||
- When your feature is ready, merge it back into the **Develop Branch**.
|
||||
|
||||
3. **Prepare a Release:**
|
||||
- When the team decides to release a version, a **Release Branch** is created from **Develop**.
|
||||
- Final adjustments are made before merging it into **Main**.
|
||||
|
||||
4. **Fix Urgent Problems:**
|
||||
- If a critical issue is found in production, create a **Hotfix Branch** from **Main**, fix it, and merge it into both **Main** and **Develop**.
|
||||
|
||||
This system helps keep work organized, ensures stability for the live version, and allows teams to work on different features or fixes simultaneously. It’s designed to make collaboration and code integration smoother.
|
||||
|
||||
---
|
||||
|
||||
## Aditional notes
|
||||
|
||||
**On start the app asks wheather to pull from git or not.**
|
||||
|
||||
- If you are connected to the remote repository, type "y" or "yes" to perform the pull action. The app will automatically abort the start process if the pull operation encounters any issues to prevent abnormalities.
|
||||
- If you are not connected to the remote repository, type "n" or "no" to skip the pull action and proceed with starting the app.
|
||||
## Git basic Workflow
|
||||
|
||||
<p align="center">
|
||||
<img src="../assets/images/gitWorkFlow.svg" alt="Git basic Workflow">
|
||||
<p align="center">Fig.1 - Git basic Workflow</p>
|
||||
</p>
|
||||
|
||||
---
|
||||
|
||||
## Git Commands
|
||||
|
||||
1. Initialize git:
|
||||
|
||||
```bash
|
||||
git init
|
||||
```
|
||||
|
||||
2. Check remote repository:
|
||||
|
||||
```bash
|
||||
git remote
|
||||
git remote -v
|
||||
```
|
||||
|
||||
3. Connect to remote repository (optional):
|
||||
If remote repository is not connected use,
|
||||
|
||||
```bash
|
||||
git remote add origin http://185.100.212.76:7776/Vishnu/modeling_app.git
|
||||
```
|
||||
|
||||
To change url use,
|
||||
|
||||
```bash
|
||||
git remote set-url origin http://185.100.212.76:7776/Vishnu/modeling_app.git
|
||||
```
|
||||
|
||||
4. Fetch from origin:
|
||||
|
||||
On fetch mention --all to pull all branches or mention remote_name and branch_name to fetch a particular branch.
|
||||
Use --force to perform force Pull.
|
||||
|
||||
```bash
|
||||
git fetch
|
||||
git pull
|
||||
```
|
||||
|
||||
5. Staging and Commit changes:
|
||||
|
||||
- "." indicated all changed files will be staged.
|
||||
- For, specific file replace "." with the path to the specific file or directory(folder).
|
||||
- "commit message" - replace text in this phrase to your commit discription.
|
||||
|
||||
**Staging**
|
||||
|
||||
```bash
|
||||
git add .
|
||||
git add path/to/directory_or_file
|
||||
```
|
||||
|
||||
To Unstage all files that were added to the staging area but does not affect the working directory:
|
||||
|
||||
```bash
|
||||
git reset
|
||||
```
|
||||
|
||||
**Commit**
|
||||
|
||||
```bash
|
||||
git commit -m "commit message"
|
||||
```
|
||||
|
||||
Creates a new commit that undoes the changes introduced by the specified commit, use --hard for discarding all changes since that commit (Warning: This command can permanently delete data.):
|
||||
|
||||
```bash
|
||||
git revert commit_hash
|
||||
```
|
||||
|
||||
6. Inspecting Commits:
|
||||
|
||||
- View the commit history:
|
||||
|
||||
```bash
|
||||
git log
|
||||
```
|
||||
|
||||
- View a graph of commits:
|
||||
|
||||
```bash
|
||||
git log --graph --oneline --all
|
||||
```
|
||||
|
||||
7. Push to remote repository:
|
||||
|
||||
- If the branch is creted for the first time and dose not located in the remote repsitory. Use,
|
||||
|
||||
```bash
|
||||
git push --set-upstream origin new_branch_name
|
||||
```
|
||||
|
||||
- Normal push
|
||||
|
||||
```bash
|
||||
git push origin new_branch_name
|
||||
```
|
||||
|
||||
- Force push and Safer force push
|
||||
|
||||
```bash
|
||||
git push --force
|
||||
git push --force-with-lease
|
||||
```
|
||||
|
||||
- Delete branch in remote repository
|
||||
|
||||
```bash
|
||||
git push origin --delete branch_name
|
||||
```
|
||||
|
||||
8. Creating and Switching Branches:
|
||||
|
||||
- To check current branch name. Use,
|
||||
|
||||
```bash
|
||||
git remote
|
||||
```
|
||||
|
||||
- To checkout from current branch and move to another branch. Use,
|
||||
|
||||
```bash
|
||||
git checkout branch_name
|
||||
```
|
||||
|
||||
- To checkout from current branch and create new branch. Use,
|
||||
|
||||
```bash
|
||||
git checkout -b new_branch_name
|
||||
```
|
||||
|
||||
9. Merging branches:
|
||||
|
||||
- Merges the specified branch to the current active branch:
|
||||
|
||||
```bash
|
||||
git merge branch_name
|
||||
```
|
||||
|
||||
- This will only perform the merge if it can be fast-forwarded. If a fast-forward isn't possible, Git will abort the merge:
|
||||
|
||||
```bash
|
||||
git merge --ff-only branch_name
|
||||
```
|
||||
|
||||
- Provide a custom merge commit message:
|
||||
|
||||
```bash
|
||||
git merge -m "Custom merge message" branch_name
|
||||
```
|
||||
|
||||
- Merge without committing automatically:
|
||||
|
||||
```bash
|
||||
git merge --no-commit branch_name
|
||||
```
|
||||
|
||||
- Continue the merge after resolving conflicts:
|
||||
|
||||
```bash
|
||||
git merge --continue
|
||||
```
|
||||
|
||||
- Abort a merge in progress:
|
||||
|
||||
```bash
|
||||
git merge --abort
|
||||
```
|
||||
|
||||
10. Stash changes:
|
||||
|
||||
- Stashes changes made in current branch:
|
||||
|
||||
```bash
|
||||
git stash
|
||||
```
|
||||
|
||||
- Stashes changes made in current branch with message:
|
||||
|
||||
```bash
|
||||
git stash save "message"
|
||||
```
|
||||
|
||||
- Apply Latest stash and apply and remove form stash list:
|
||||
|
||||
```bash
|
||||
git stash apply
|
||||
git stash pop
|
||||
```
|
||||
|
||||
- View Stash:
|
||||
|
||||
```bash
|
||||
git stash list
|
||||
```
|
||||
|
||||
- Clear all stashes:
|
||||
|
||||
```bash
|
||||
git stash clear
|
||||
```
|
||||
|
||||
- Remove the latest stash:
|
||||
|
||||
```bash
|
||||
git stash drop
|
||||
```
|
||||
|
||||
11. Branch commands:
|
||||
|
||||
- To View all local and remote branches
|
||||
|
||||
```bash
|
||||
git branch
|
||||
git branch -a
|
||||
```
|
||||
|
||||
- To Create branch
|
||||
|
||||
```bash
|
||||
git branch branch_name
|
||||
```
|
||||
|
||||
- To switch between branch
|
||||
|
||||
```bash
|
||||
git checkout branch_name
|
||||
```
|
||||
|
||||
Alternatively if you want to bring the changes to the new branch,
|
||||
|
||||
```bash
|
||||
git switch branch_name
|
||||
```
|
||||
|
||||
- To switch between branch
|
||||
|
||||
```bash
|
||||
git checkout -b branch_name
|
||||
```
|
||||
|
||||
Alternatively,
|
||||
|
||||
```bash
|
||||
git switch -c branch_name
|
||||
```
|
||||
|
||||
- To rename branch
|
||||
|
||||
```bash
|
||||
git branch -m old_branch_name new_branch_name
|
||||
```
|
||||
|
||||
- To Delete branch (use -D to force delete)
|
||||
|
||||
```bash
|
||||
git branch -d branch_name
|
||||
```
|
||||
|
||||
12. Other git comments consiered usefull in previous encounters:
|
||||
|
||||
- press q to exit git response
|
||||
|
||||
- Prevent git from creating **zoneIdentifier** files
|
||||
|
||||
```bash
|
||||
git config core.protectNTFS false
|
||||
```
|
||||
---
|
||||
|
||||
## Git Branching
|
||||
|
||||
<p align="center">
|
||||
<img src="../assets/images/gitBranching.svg" alt="Git Branching">
|
||||
<p align="center">Fig.2 - Git Branching</p>
|
||||
</p>
|
||||
|
||||
This diagram represents a branching model for managing the development of a software project. It uses different branches to organize and control how code changes are developed, tested, and released. Here’s a breakdown of the key concepts, simplified for someone new:
|
||||
|
||||
### **Main Components**
|
||||
1. **Main Branch** (blue line):
|
||||
- This branch represents the "production" or "live" version of the project.
|
||||
- Only stable and tested versions of the code are added here.
|
||||
- Releases like `v0.1`, `v0.2`, and `v1.0` are tagged here.
|
||||
|
||||
2. **Develop Branch** (purple line):
|
||||
- This is where active development happens.
|
||||
- Features or fixes are integrated here before they are prepared for a release.
|
||||
- It acts as a staging area for new work to ensure it’s functional and complete.
|
||||
|
||||
3. **Feature Branches** (green lines):
|
||||
- These branches are used to develop specific features or tasks.
|
||||
- Developers create a new branch for each feature and work on it independently.
|
||||
- Once complete, they are merged into the **Develop Branch**.
|
||||
|
||||
4. **Release Branch** (teal line):
|
||||
- Before a release is finalized, a release branch is created.
|
||||
- Final fixes and testing are done here to ensure stability.
|
||||
- Once complete, it is merged into both **Main** and **Develop** branches to mark the release.
|
||||
|
||||
5. **Hotfix Branch** (red line):
|
||||
- This branch is for urgent fixes to the live code.
|
||||
- If an issue is found in the **Main Branch** (e.g., a bug in `v0.1`), a **Hotfix Branch** is created.
|
||||
- After fixing, it is merged into both **Main** and **Develop** to ensure the fix is applied everywhere.
|
||||
|
||||
### **Workflow Summary**
|
||||
1. **Start a Feature:**
|
||||
- Create a feature branch from the **Develop Branch**.
|
||||
- Work on your task and complete it.
|
||||
|
||||
2. **Integrate Your Work:**
|
||||
- When your feature is ready, merge it back into the **Develop Branch**.
|
||||
|
||||
3. **Prepare a Release:**
|
||||
- When the team decides to release a version, a **Release Branch** is created from **Develop**.
|
||||
- Final adjustments are made before merging it into **Main**.
|
||||
|
||||
4. **Fix Urgent Problems:**
|
||||
- If a critical issue is found in production, create a **Hotfix Branch** from **Main**, fix it, and merge it into both **Main** and **Develop**.
|
||||
|
||||
This system helps keep work organized, ensures stability for the live version, and allows teams to work on different features or fixes simultaneously. It’s designed to make collaboration and code integration smoother.
|
||||
|
||||
---
|
||||
|
||||
## Aditional notes
|
||||
|
||||
**On start the app asks wheather to pull from git or not.**
|
||||
|
||||
- If you are connected to the remote repository, type "y" or "yes" to perform the pull action. The app will automatically abort the start process if the pull operation encounters any issues to prevent abnormalities.
|
||||
- If you are not connected to the remote repository, type "n" or "no" to skip the pull action and proceed with starting the app.
|
||||
|
||||
@@ -1,197 +1,197 @@
|
||||
# How to Write a Markdown (.md) File for Large-Scale Projects
|
||||
|
||||
## Introduction
|
||||
|
||||
Markdown (MD) is a lightweight markup language widely used in software development for documentation purposes. It is simple, easy to read, and can be converted to HTML, making it ideal for collaborative and large-scale projects. This guide will help you create well-structured, clear, and professional Markdown files tailored for use in large-scale projects.
|
||||
|
||||
---
|
||||
|
||||
## Obsidian
|
||||
|
||||
#### What is Obsidian
|
||||
|
||||
Obsidian is a powerful, feature-rich note-taking and knowledge management application designed for individuals and teams. It is highly customizable and based on plain text Markdown files, making it a versatile tool for creating, organizing, and connecting ideas. It’s particularly popular among professionals, students, and researchers who use it for personal knowledge management (PKM), journaling, writing, and task management.
|
||||
|
||||
All notes in Obsidian are plain text Markdown files stored locally on your computer. This ensures that your notes are portable, future-proof, and easy to access outside the application.
|
||||
|
||||
#### How to Get Started
|
||||
1. Download and Install:
|
||||
- [Obsidian’s website](https://obsidian.md/) provides downloads for all major platforms.
|
||||
|
||||
2. Create a Vault:
|
||||
- A "vault" is a folder where all your Markdown notes are stored. You can have multiple vaults for different purposes.
|
||||
|
||||
3. Start Taking Notes:
|
||||
- Create new notes using Markdown syntax and link them using Note Name.
|
||||
|
||||
4. For more:
|
||||
- [Read official Obsidian documentation](https://help.obsidian.md/Home)
|
||||
|
||||
---
|
||||
|
||||
## Understanding Markdown Syntax
|
||||
|
||||
Markdown provides a straightforward syntax for formatting text. Below are the essential elements you'll need to master:
|
||||
|
||||
#### **1. Headers**
|
||||
|
||||
Headers define the structure of your document. Use `#` to denote headings, with more `#` symbols indicating smaller headings.
|
||||
|
||||
- Example:
|
||||
|
||||
```markdown
|
||||
# Main Header
|
||||
|
||||
## Subheader
|
||||
|
||||
### Sub-Subheader
|
||||
```
|
||||
|
||||
#### **2. Lists**
|
||||
|
||||
Markdown supports ordered and unordered lists for organizing information.
|
||||
|
||||
- Unordered list:
|
||||
```markdown
|
||||
- Item 1
|
||||
- Item 2
|
||||
```
|
||||
- Ordered list:
|
||||
```markdown
|
||||
1. Step 1
|
||||
2. Step 2
|
||||
```
|
||||
|
||||
#### **3. Text Formatting**
|
||||
|
||||
- Bold: `**bold text**`
|
||||
- Italic: `*italic text*`
|
||||
- Inline Code: `` `inline code` ``
|
||||
- Strikethrough: `~~strikethrough~~`
|
||||
|
||||
#### **4. Links and Images**
|
||||
|
||||
- Link: `[Link Text](URL)`
|
||||
- Image: ``
|
||||
|
||||
#### **5. Tables**
|
||||
|
||||
Tables are useful for presenting structured data.
|
||||
|
||||
- Example:
|
||||
```markdown
|
||||
| Header 1 | Header 2 |
|
||||
| -------- | -------- |
|
||||
| Row 1 | Data |
|
||||
| Row 2 | Data |
|
||||
```
|
||||
|
||||
#### **6. Code Blocks**
|
||||
|
||||
Use triple backticks to include code blocks. Specify the programming language for syntax highlighting.
|
||||
|
||||
- Example:
|
||||
````markdown
|
||||
```jsx
|
||||
console.log("Hello world");
|
||||
```
|
||||
````
|
||||
|
||||
---
|
||||
|
||||
## Key Guidelines for Large-Scale Projects
|
||||
|
||||
#### **1. Understand the Project Structure**
|
||||
|
||||
Before you start writing, familiarize yourself with the project's directory and the purpose of the documentation. In large projects, MD files often serve specific roles, such as:
|
||||
|
||||
- **README.md**: A high-level overview of the project.
|
||||
- **CONTRIBUTING.md**: Guidelines for contributing to the project.
|
||||
- **CHANGELOG.md**: Records of changes made over time.
|
||||
- **API.md**: Detailed documentation of APIs.
|
||||
|
||||
#### **2. Write for Your Audience**
|
||||
|
||||
- Identify the primary readers of the document (e.g., developers, testers, stakeholders).
|
||||
- Use technical terms appropriately and explain jargon when necessary.
|
||||
|
||||
#### **3. Maintain Consistency**
|
||||
|
||||
- Follow a consistent style, tone, and structure across all Markdown files.
|
||||
- Adhere to any project-specific conventions or style guides.
|
||||
|
||||
#### **4. Use Comments Wisely**
|
||||
|
||||
Markdown does not support native comments, but you can use HTML-style comments for notes that should not appear in the rendered file.
|
||||
|
||||
- Example:
|
||||
```markdown
|
||||
<!-- This is a comment -->
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Checklist for Writing a Quality Markdown File
|
||||
|
||||
1. **Start with a Purpose**
|
||||
Begin your document with a clear purpose or summary of what it covers.
|
||||
|
||||
2. **Follow a Logical Flow**
|
||||
Organize information hierarchically using headings and subheadings.
|
||||
|
||||
3. **Be Concise and Clear**
|
||||
Avoid redundant information. Use simple language where possible.
|
||||
|
||||
4. **Incorporate Visual Aids**
|
||||
Use tables, images, or diagrams to explain complex topics.
|
||||
|
||||
5. **Test and Validate**
|
||||
|
||||
- Render your Markdown file locally or in the project’s preferred Markdown viewer to ensure formatting works as expected.
|
||||
- Use tools like **MarkdownLint** to check for common syntax errors.
|
||||
|
||||
6. **Link to Other Resources**
|
||||
Provide hyperlinks to related MD files or external resources.
|
||||
|
||||
7. **Version Control**
|
||||
Use version control (e.g., Git) to track changes and maintain a clear history of updates.
|
||||
|
||||
---
|
||||
|
||||
## Example: Simple README.md Template
|
||||
|
||||
````markdown
|
||||
# Project Title
|
||||
|
||||
## Description
|
||||
|
||||
Provide a brief overview of the project, its purpose, and key features.
|
||||
|
||||
## Installation
|
||||
|
||||
Explain how to set up the project.
|
||||
|
||||
```bash
|
||||
# Example installation command
|
||||
pip install project-name
|
||||
```
|
||||
````
|
||||
|
||||
## Usage
|
||||
|
||||
Provide examples of how to use the project.
|
||||
|
||||
```python
|
||||
# Example usage
|
||||
from project import feature
|
||||
feature.run()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## External Resources
|
||||
|
||||
1. ▶️ [The Only Markdown Crash Course You Will Ever Need](https://www.youtube.com/watch?v=_PPWWRV6gbA&ab_channel=WebDevSimplified)
|
||||
2. 📄 [Basic writing and formatting syntax](https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax)
|
||||
|
||||
---
|
||||
# How to Write a Markdown (.md) File for Large-Scale Projects
|
||||
|
||||
## Introduction
|
||||
|
||||
Markdown (MD) is a lightweight markup language widely used in software development for documentation purposes. It is simple, easy to read, and can be converted to HTML, making it ideal for collaborative and large-scale projects. This guide will help you create well-structured, clear, and professional Markdown files tailored for use in large-scale projects.
|
||||
|
||||
---
|
||||
|
||||
## Obsidian
|
||||
|
||||
#### What is Obsidian
|
||||
|
||||
Obsidian is a powerful, feature-rich note-taking and knowledge management application designed for individuals and teams. It is highly customizable and based on plain text Markdown files, making it a versatile tool for creating, organizing, and connecting ideas. It’s particularly popular among professionals, students, and researchers who use it for personal knowledge management (PKM), journaling, writing, and task management.
|
||||
|
||||
All notes in Obsidian are plain text Markdown files stored locally on your computer. This ensures that your notes are portable, future-proof, and easy to access outside the application.
|
||||
|
||||
#### How to Get Started
|
||||
1. Download and Install:
|
||||
- [Obsidian’s website](https://obsidian.md/) provides downloads for all major platforms.
|
||||
|
||||
2. Create a Vault:
|
||||
- A "vault" is a folder where all your Markdown notes are stored. You can have multiple vaults for different purposes.
|
||||
|
||||
3. Start Taking Notes:
|
||||
- Create new notes using Markdown syntax and link them using Note Name.
|
||||
|
||||
4. For more:
|
||||
- [Read official Obsidian documentation](https://help.obsidian.md/Home)
|
||||
|
||||
---
|
||||
|
||||
## Understanding Markdown Syntax
|
||||
|
||||
Markdown provides a straightforward syntax for formatting text. Below are the essential elements you'll need to master:
|
||||
|
||||
#### **1. Headers**
|
||||
|
||||
Headers define the structure of your document. Use `#` to denote headings, with more `#` symbols indicating smaller headings.
|
||||
|
||||
- Example:
|
||||
|
||||
```markdown
|
||||
# Main Header
|
||||
|
||||
## Subheader
|
||||
|
||||
### Sub-Subheader
|
||||
```
|
||||
|
||||
#### **2. Lists**
|
||||
|
||||
Markdown supports ordered and unordered lists for organizing information.
|
||||
|
||||
- Unordered list:
|
||||
```markdown
|
||||
- Item 1
|
||||
- Item 2
|
||||
```
|
||||
- Ordered list:
|
||||
```markdown
|
||||
1. Step 1
|
||||
2. Step 2
|
||||
```
|
||||
|
||||
#### **3. Text Formatting**
|
||||
|
||||
- Bold: `**bold text**`
|
||||
- Italic: `*italic text*`
|
||||
- Inline Code: `` `inline code` ``
|
||||
- Strikethrough: `~~strikethrough~~`
|
||||
|
||||
#### **4. Links and Images**
|
||||
|
||||
- Link: `[Link Text](URL)`
|
||||
- Image: ``
|
||||
|
||||
#### **5. Tables**
|
||||
|
||||
Tables are useful for presenting structured data.
|
||||
|
||||
- Example:
|
||||
```markdown
|
||||
| Header 1 | Header 2 |
|
||||
| -------- | -------- |
|
||||
| Row 1 | Data |
|
||||
| Row 2 | Data |
|
||||
```
|
||||
|
||||
#### **6. Code Blocks**
|
||||
|
||||
Use triple backticks to include code blocks. Specify the programming language for syntax highlighting.
|
||||
|
||||
- Example:
|
||||
````markdown
|
||||
```jsx
|
||||
console.log("Hello world");
|
||||
```
|
||||
````
|
||||
|
||||
---
|
||||
|
||||
## Key Guidelines for Large-Scale Projects
|
||||
|
||||
#### **1. Understand the Project Structure**
|
||||
|
||||
Before you start writing, familiarize yourself with the project's directory and the purpose of the documentation. In large projects, MD files often serve specific roles, such as:
|
||||
|
||||
- **README.md**: A high-level overview of the project.
|
||||
- **CONTRIBUTING.md**: Guidelines for contributing to the project.
|
||||
- **CHANGELOG.md**: Records of changes made over time.
|
||||
- **API.md**: Detailed documentation of APIs.
|
||||
|
||||
#### **2. Write for Your Audience**
|
||||
|
||||
- Identify the primary readers of the document (e.g., developers, testers, stakeholders).
|
||||
- Use technical terms appropriately and explain jargon when necessary.
|
||||
|
||||
#### **3. Maintain Consistency**
|
||||
|
||||
- Follow a consistent style, tone, and structure across all Markdown files.
|
||||
- Adhere to any project-specific conventions or style guides.
|
||||
|
||||
#### **4. Use Comments Wisely**
|
||||
|
||||
Markdown does not support native comments, but you can use HTML-style comments for notes that should not appear in the rendered file.
|
||||
|
||||
- Example:
|
||||
```markdown
|
||||
<!-- This is a comment -->
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Checklist for Writing a Quality Markdown File
|
||||
|
||||
1. **Start with a Purpose**
|
||||
Begin your document with a clear purpose or summary of what it covers.
|
||||
|
||||
2. **Follow a Logical Flow**
|
||||
Organize information hierarchically using headings and subheadings.
|
||||
|
||||
3. **Be Concise and Clear**
|
||||
Avoid redundant information. Use simple language where possible.
|
||||
|
||||
4. **Incorporate Visual Aids**
|
||||
Use tables, images, or diagrams to explain complex topics.
|
||||
|
||||
5. **Test and Validate**
|
||||
|
||||
- Render your Markdown file locally or in the project’s preferred Markdown viewer to ensure formatting works as expected.
|
||||
- Use tools like **MarkdownLint** to check for common syntax errors.
|
||||
|
||||
6. **Link to Other Resources**
|
||||
Provide hyperlinks to related MD files or external resources.
|
||||
|
||||
7. **Version Control**
|
||||
Use version control (e.g., Git) to track changes and maintain a clear history of updates.
|
||||
|
||||
---
|
||||
|
||||
## Example: Simple README.md Template
|
||||
|
||||
````markdown
|
||||
# Project Title
|
||||
|
||||
## Description
|
||||
|
||||
Provide a brief overview of the project, its purpose, and key features.
|
||||
|
||||
## Installation
|
||||
|
||||
Explain how to set up the project.
|
||||
|
||||
```bash
|
||||
# Example installation command
|
||||
pip install project-name
|
||||
```
|
||||
````
|
||||
|
||||
## Usage
|
||||
|
||||
Provide examples of how to use the project.
|
||||
|
||||
```python
|
||||
# Example usage
|
||||
from project import feature
|
||||
feature.run()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## External Resources
|
||||
|
||||
1. ▶️ [The Only Markdown Crash Course You Will Ever Need](https://www.youtube.com/watch?v=_PPWWRV6gbA&ab_channel=WebDevSimplified)
|
||||
2. 📄 [Basic writing and formatting syntax](https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax)
|
||||
|
||||
---
|
||||
|
||||
@@ -1,176 +1,176 @@
|
||||
## /temp/ files
|
||||
|
||||
I’ve set up the repository to ignore all folders named `temp`. This is a space you can use for temporary or experimental files without worrying about affecting the application or pushing these files to Gitea.
|
||||
|
||||
- **Purpose**: Use the `temp` folder to store temporary files, logs, or scripts for testing purposes.
|
||||
- **Ignored by Git**: All `temp` folders are excluded from version control using the `.gitignore` rule `**/temp/`. These files will not be committed or pushed to the repository.
|
||||
- **Best Practices**: Please avoid storing critical or shared files here. Use it for temporary work only, and clean up files when no longer needed.
|
||||
|
||||
This should help us maintain a clean repository while giving everyone the freedom to experiment.
|
||||
|
||||
---
|
||||
|
||||
## Standardized File Structure and Coding Guidelines
|
||||
|
||||
To improve our codebase's readability, maintainability, and scalability, we’re adopting a standardized file structure and coding practices. Here’s a summary:
|
||||
|
||||
1. **File Structure**:
|
||||
|
||||
- **Components (`.tsx`)**: All files that return HTML/JSX will be React components with a `.tsx` extension. They should focus on UI logic and presentation.
|
||||
|
||||
- **Purpose**: Files with the `.tsx` extension are reserved for React components that return JSX/HTML.
|
||||
- **Location**: Components should be organized based on their purpose or scope within the application. Example:
|
||||
```
|
||||
src/
|
||||
components/
|
||||
Header.tsx
|
||||
Footer.tsx
|
||||
pages/
|
||||
Home.tsx
|
||||
About.tsx
|
||||
```
|
||||
- **Naming Convention**: Component filenames should use PascalCase, matching the component name (e.g., `Header.tsx` for a `Header` component).
|
||||
- **Example**:
|
||||
|
||||
```tsx
|
||||
/**
|
||||
* Header component that displays the application's navigation bar.
|
||||
*
|
||||
* @returns {JSX.Element} A navigation bar with links.
|
||||
*/
|
||||
const Header: React.FC = () => {
|
||||
return (
|
||||
<header>
|
||||
<nav>
|
||||
<a href="/">Home</a>
|
||||
<a href="/about">About</a>
|
||||
</nav>
|
||||
</header>
|
||||
);
|
||||
};
|
||||
|
||||
export default Header;
|
||||
```
|
||||
|
||||
- **Functions (`.ts`)**: All helper functions or business logic will go into `.ts` files, separate from the components.
|
||||
- **Purpose**: Files with the `.ts` extension are for helper functions, utility logic, or other non-React code that does not return JSX/HTML.
|
||||
- **Location**: Place utility files in a `utils` directory or within a directory relevant to their context. Example:
|
||||
```
|
||||
src/
|
||||
utils/
|
||||
calculateTotal.ts
|
||||
formatDate.ts
|
||||
hooks/
|
||||
useFetch.ts
|
||||
```
|
||||
- **Naming Convention**: Use camelCase for filenames that describe the primary function (e.g., `calculateTotal.ts` for a `calculateTotal` function).
|
||||
- **Example**:
|
||||
```ts
|
||||
/**
|
||||
* Calculates the total price based on items and tax rate.
|
||||
*
|
||||
* @param {number[]} prices - Array of item prices.
|
||||
* @param {number} taxRate - Tax rate as a decimal.
|
||||
* @returns {number} Total price including tax.
|
||||
*/
|
||||
export const calculateTotal = (
|
||||
prices: number[],
|
||||
taxRate: number
|
||||
): number => {
|
||||
const subtotal = prices.reduce((sum, price) => sum + price, 0);
|
||||
return subtotal + subtotal * taxRate;
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Commenting Standards
|
||||
|
||||
To improve code readability and understanding, every function and component should include comments explaining its purpose, inputs, and outputs. Here’s how we’ll approach this:
|
||||
|
||||
1. **React Components**:
|
||||
|
||||
- Include a **high-level description** of what the component does.
|
||||
- Specify the **props** it accepts, along with their types.
|
||||
- Specify the **return type** (`JSX.Element` for React components).
|
||||
|
||||
**Example**:
|
||||
|
||||
```tsx
|
||||
/**
|
||||
* Button component that triggers an action when clicked.
|
||||
*
|
||||
* @param {object} props - The props object.
|
||||
* @param {string} props.label - The text to display on the button.
|
||||
* @param {() => void} props.onClick - The callback function triggered when the button is clicked.
|
||||
* @returns {JSX.Element} A styled button element.
|
||||
*/
|
||||
const Button: React.FC<{ label: string; onClick: () => void }> = ({
|
||||
label,
|
||||
onClick,
|
||||
}) => {
|
||||
return <button onClick={onClick}>{label}</button>;
|
||||
};
|
||||
|
||||
export default Button;
|
||||
```
|
||||
|
||||
2. **Functions**:
|
||||
|
||||
- Include a **detailed description** of what the function does.
|
||||
- Specify the **parameters**, their types, and what they represent.
|
||||
- Specify the **return type** and what it represents.
|
||||
|
||||
**Example**:
|
||||
|
||||
```ts
|
||||
/**
|
||||
* Converts a date string to a readable format.
|
||||
*
|
||||
* @param {string} date - A date string in ISO format (e.g., "2024-11-21").
|
||||
* @returns {string} The formatted date (e.g., "November 21, 2024").
|
||||
*/
|
||||
export const formatDate = (date: string): string => {
|
||||
const options: Intl.DateTimeFormatOptions = {
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
day: "numeric",
|
||||
};
|
||||
return new Date(date).toLocaleDateString(undefined, options);
|
||||
};
|
||||
```
|
||||
|
||||
3. **Commenting Standards**:
|
||||
|
||||
- Each function and component should have a comment explaining:
|
||||
- What it does.
|
||||
- The props or parameters it accepts (including types).
|
||||
- What it returns and why.
|
||||
|
||||
4. **Why This Matters**:
|
||||
- This structure helps us maintain a clean, modular codebase. It also makes the project easier to navigate for new and existing team members.
|
||||
|
||||
---
|
||||
|
||||
## File Naming Guidelines
|
||||
|
||||
To maintain consistency and professionalism in our codebase, we are standardizing the way we name files. This is crucial for readability, collaboration, and avoiding potential issues across different operating systems.
|
||||
We are introducing a standardized **file naming convention** to maintain consistency across the codebase. Here are the key points:
|
||||
|
||||
1. **File Naming**:
|
||||
|
||||
- All file names must follow the **camelCase** convention (e.g., `headerComponent.tsx`, `calculateTotal.ts`).
|
||||
- This ensures uniformity and avoids case sensitivity issues.
|
||||
|
||||
2. **Pre-Commit Hook**:
|
||||
|
||||
- A pre-commit hook will automatically rename files to camelCase if they don’t comply.
|
||||
- Please double-check that your imports match the updated file names to avoid runtime errors.
|
||||
|
||||
3. **What You Need to Do**:
|
||||
|
||||
- Name files correctly in camelCase from the start.
|
||||
- If a file is renamed during a commit, ensure all imports are updated to reflect the new name.
|
||||
|
||||
4. **Why This Matters**:
|
||||
- Consistent file naming improves readability and reduces issues when working in teams or across different operating systems.
|
||||
## /temp/ files
|
||||
|
||||
I’ve set up the repository to ignore all folders named `temp`. This is a space you can use for temporary or experimental files without worrying about affecting the application or pushing these files to Gitea.
|
||||
|
||||
- **Purpose**: Use the `temp` folder to store temporary files, logs, or scripts for testing purposes.
|
||||
- **Ignored by Git**: All `temp` folders are excluded from version control using the `.gitignore` rule `**/temp/`. These files will not be committed or pushed to the repository.
|
||||
- **Best Practices**: Please avoid storing critical or shared files here. Use it for temporary work only, and clean up files when no longer needed.
|
||||
|
||||
This should help us maintain a clean repository while giving everyone the freedom to experiment.
|
||||
|
||||
---
|
||||
|
||||
## Standardized File Structure and Coding Guidelines
|
||||
|
||||
To improve our codebase's readability, maintainability, and scalability, we’re adopting a standardized file structure and coding practices. Here’s a summary:
|
||||
|
||||
1. **File Structure**:
|
||||
|
||||
- **Components (`.tsx`)**: All files that return HTML/JSX will be React components with a `.tsx` extension. They should focus on UI logic and presentation.
|
||||
|
||||
- **Purpose**: Files with the `.tsx` extension are reserved for React components that return JSX/HTML.
|
||||
- **Location**: Components should be organized based on their purpose or scope within the application. Example:
|
||||
```
|
||||
src/
|
||||
components/
|
||||
Header.tsx
|
||||
Footer.tsx
|
||||
pages/
|
||||
Home.tsx
|
||||
About.tsx
|
||||
```
|
||||
- **Naming Convention**: Component filenames should use PascalCase, matching the component name (e.g., `Header.tsx` for a `Header` component).
|
||||
- **Example**:
|
||||
|
||||
```tsx
|
||||
/**
|
||||
* Header component that displays the application's navigation bar.
|
||||
*
|
||||
* @returns {JSX.Element} A navigation bar with links.
|
||||
*/
|
||||
const Header: React.FC = () => {
|
||||
return (
|
||||
<header>
|
||||
<nav>
|
||||
<a href="/">Home</a>
|
||||
<a href="/about">About</a>
|
||||
</nav>
|
||||
</header>
|
||||
);
|
||||
};
|
||||
|
||||
export default Header;
|
||||
```
|
||||
|
||||
- **Functions (`.ts`)**: All helper functions or business logic will go into `.ts` files, separate from the components.
|
||||
- **Purpose**: Files with the `.ts` extension are for helper functions, utility logic, or other non-React code that does not return JSX/HTML.
|
||||
- **Location**: Place utility files in a `utils` directory or within a directory relevant to their context. Example:
|
||||
```
|
||||
src/
|
||||
utils/
|
||||
calculateTotal.ts
|
||||
formatDate.ts
|
||||
hooks/
|
||||
useFetch.ts
|
||||
```
|
||||
- **Naming Convention**: Use camelCase for filenames that describe the primary function (e.g., `calculateTotal.ts` for a `calculateTotal` function).
|
||||
- **Example**:
|
||||
```ts
|
||||
/**
|
||||
* Calculates the total price based on items and tax rate.
|
||||
*
|
||||
* @param {number[]} prices - Array of item prices.
|
||||
* @param {number} taxRate - Tax rate as a decimal.
|
||||
* @returns {number} Total price including tax.
|
||||
*/
|
||||
export const calculateTotal = (
|
||||
prices: number[],
|
||||
taxRate: number
|
||||
): number => {
|
||||
const subtotal = prices.reduce((sum, price) => sum + price, 0);
|
||||
return subtotal + subtotal * taxRate;
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Commenting Standards
|
||||
|
||||
To improve code readability and understanding, every function and component should include comments explaining its purpose, inputs, and outputs. Here’s how we’ll approach this:
|
||||
|
||||
1. **React Components**:
|
||||
|
||||
- Include a **high-level description** of what the component does.
|
||||
- Specify the **props** it accepts, along with their types.
|
||||
- Specify the **return type** (`JSX.Element` for React components).
|
||||
|
||||
**Example**:
|
||||
|
||||
```tsx
|
||||
/**
|
||||
* Button component that triggers an action when clicked.
|
||||
*
|
||||
* @param {object} props - The props object.
|
||||
* @param {string} props.label - The text to display on the button.
|
||||
* @param {() => void} props.onClick - The callback function triggered when the button is clicked.
|
||||
* @returns {JSX.Element} A styled button element.
|
||||
*/
|
||||
const Button: React.FC<{ label: string; onClick: () => void }> = ({
|
||||
label,
|
||||
onClick,
|
||||
}) => {
|
||||
return <button onClick={onClick}>{label}</button>;
|
||||
};
|
||||
|
||||
export default Button;
|
||||
```
|
||||
|
||||
2. **Functions**:
|
||||
|
||||
- Include a **detailed description** of what the function does.
|
||||
- Specify the **parameters**, their types, and what they represent.
|
||||
- Specify the **return type** and what it represents.
|
||||
|
||||
**Example**:
|
||||
|
||||
```ts
|
||||
/**
|
||||
* Converts a date string to a readable format.
|
||||
*
|
||||
* @param {string} date - A date string in ISO format (e.g., "2024-11-21").
|
||||
* @returns {string} The formatted date (e.g., "November 21, 2024").
|
||||
*/
|
||||
export const formatDate = (date: string): string => {
|
||||
const options: Intl.DateTimeFormatOptions = {
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
day: "numeric",
|
||||
};
|
||||
return new Date(date).toLocaleDateString(undefined, options);
|
||||
};
|
||||
```
|
||||
|
||||
3. **Commenting Standards**:
|
||||
|
||||
- Each function and component should have a comment explaining:
|
||||
- What it does.
|
||||
- The props or parameters it accepts (including types).
|
||||
- What it returns and why.
|
||||
|
||||
4. **Why This Matters**:
|
||||
- This structure helps us maintain a clean, modular codebase. It also makes the project easier to navigate for new and existing team members.
|
||||
|
||||
---
|
||||
|
||||
## File Naming Guidelines
|
||||
|
||||
To maintain consistency and professionalism in our codebase, we are standardizing the way we name files. This is crucial for readability, collaboration, and avoiding potential issues across different operating systems.
|
||||
We are introducing a standardized **file naming convention** to maintain consistency across the codebase. Here are the key points:
|
||||
|
||||
1. **File Naming**:
|
||||
|
||||
- All file names must follow the **camelCase** convention (e.g., `headerComponent.tsx`, `calculateTotal.ts`).
|
||||
- This ensures uniformity and avoids case sensitivity issues.
|
||||
|
||||
2. **Pre-Commit Hook**:
|
||||
|
||||
- A pre-commit hook will automatically rename files to camelCase if they don’t comply.
|
||||
- Please double-check that your imports match the updated file names to avoid runtime errors.
|
||||
|
||||
3. **What You Need to Do**:
|
||||
|
||||
- Name files correctly in camelCase from the start.
|
||||
- If a file is renamed during a commit, ensure all imports are updated to reflect the new name.
|
||||
|
||||
4. **Why This Matters**:
|
||||
- Consistent file naming improves readability and reduces issues when working in teams or across different operating systems.
|
||||
|
||||
@@ -1,107 +1,107 @@
|
||||
# Project Folder Structure
|
||||
|
||||
This document provides a detailed description of the purpose of each folder in the project by root level, along with the folder hierarchy.
|
||||
|
||||
## Folder Hierarchy
|
||||
|
||||
```
|
||||
📁 src
|
||||
├── 📁 assets
|
||||
├── 📁 components
|
||||
├── 📁 functions
|
||||
├── 📁 hooks
|
||||
├── 📁 modules
|
||||
│ ├── 📁 builder
|
||||
│ ├── 📁 simulation
|
||||
│ └── 📁 visualization
|
||||
├── 📁 services
|
||||
├── 📁 store
|
||||
├── 📁 styles
|
||||
├── 📁 tests
|
||||
├── 📁 types
|
||||
├── 📁 utils
|
||||
├── App.css
|
||||
├── App.tsx
|
||||
├── index.css
|
||||
├── main.tsx
|
||||
└── vite-env.d.ts
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Description of Each Folder
|
||||
|
||||
### 📁 `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 don’t fit into other specific folders.
|
||||
- **Example:**
|
||||
- Helper functions like debouncers or date formatters.
|
||||
|
||||
---
|
||||
|
||||
## Root-Level Files
|
||||
|
||||
### `App.tsx`
|
||||
- **Purpose:** The root React component, initializing the main application layout and logic.
|
||||
|
||||
### `index.css`
|
||||
- **Purpose:** Contains global styles applied throughout the application.
|
||||
|
||||
### `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.
|
||||
# Project Folder Structure
|
||||
|
||||
This document provides a detailed description of the purpose of each folder in the project by root level, along with the folder hierarchy.
|
||||
|
||||
## Folder Hierarchy
|
||||
|
||||
```
|
||||
📁 src
|
||||
├── 📁 assets
|
||||
├── 📁 components
|
||||
├── 📁 functions
|
||||
├── 📁 hooks
|
||||
├── 📁 modules
|
||||
│ ├── 📁 builder
|
||||
│ ├── 📁 simulation
|
||||
│ └── 📁 visualization
|
||||
├── 📁 services
|
||||
├── 📁 store
|
||||
├── 📁 styles
|
||||
├── 📁 tests
|
||||
├── 📁 types
|
||||
├── 📁 utils
|
||||
├── App.css
|
||||
├── App.tsx
|
||||
├── index.css
|
||||
├── main.tsx
|
||||
└── vite-env.d.ts
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Description of Each Folder
|
||||
|
||||
### 📁 `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 don’t fit into other specific folders.
|
||||
- **Example:**
|
||||
- Helper functions like debouncers or date formatters.
|
||||
|
||||
---
|
||||
|
||||
## Root-Level Files
|
||||
|
||||
### `App.tsx`
|
||||
- **Purpose:** The root React component, initializing the main application layout and logic.
|
||||
|
||||
### `index.css`
|
||||
- **Purpose:** Contains global styles applied throughout the application.
|
||||
|
||||
### `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.
|
||||
|
||||
@@ -1,97 +1,97 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Documentation</title>
|
||||
|
||||
<!-- Docsify Vue Theme -->
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="//cdn.jsdelivr.net/npm/docsify@4/lib/themes/vue.css"
|
||||
/>
|
||||
|
||||
<!-- Link to your custom styles -->
|
||||
<link rel="stylesheet" href="./styles/style.css" />
|
||||
|
||||
<!-- Docsify Search Plugin (Make sure it's the correct version) -->
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://cdn.jsdelivr.net/npm/docsify-search@1.7.0/dist/docsify-search.min.css"
|
||||
/>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
<!-- Docsify Script -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/docsify/lib/docsify.min.js"></script>
|
||||
|
||||
<!-- Docsify Search Plugin Script -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/docsify/lib/plugins/search.min.js"></script>
|
||||
|
||||
<!-- Docsify Image Preview Plugin -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/docsify/lib/plugins/zoom-image.min.js"></script>
|
||||
|
||||
<!-- Docsify Theme Toggle Button and Search Integration -->
|
||||
<script>
|
||||
window.$docsify = {
|
||||
loadSidebar: true,
|
||||
loadNavbar: true,
|
||||
subMaxLevel: 2,
|
||||
auto2top: true,
|
||||
|
||||
search: {
|
||||
insertBefore: ".sidebar-nav", // Ensure the search bar is placed before the sidebar-nav element
|
||||
paths: "auto", // Automatically index all markdown files
|
||||
placeholder: "Search documentation...", // Placeholder text
|
||||
noData: "No results!", // Message when no results are found
|
||||
},
|
||||
|
||||
plugins: [
|
||||
function (hook, vm) {
|
||||
// Function to add the theme toggle button
|
||||
function addThemeToggle() {
|
||||
const themeButtonHtml = `
|
||||
<div class="theme-toggle-container">
|
||||
Theme :
|
||||
<button id="theme-toggle">🌙</button>
|
||||
</div>
|
||||
`;
|
||||
// Append the theme button inside the sidebar or navbar
|
||||
const sidebar = document.querySelector(".sidebar");
|
||||
if (sidebar) {
|
||||
sidebar.insertAdjacentHTML("beforebegin", themeButtonHtml);
|
||||
}
|
||||
|
||||
// Handle the theme toggle functionality
|
||||
const themeToggleBtn = document.getElementById("theme-toggle");
|
||||
themeToggleBtn.addEventListener("click", () => {
|
||||
// Toggle the dark-theme class
|
||||
const isDark = document.body.classList.toggle("dark-theme");
|
||||
themeToggleBtn.textContent = isDark ? "🌞" : "🌙";
|
||||
|
||||
// Optionally save the user's theme preference to localStorage
|
||||
localStorage.setItem("theme", isDark ? "dark" : "light");
|
||||
});
|
||||
|
||||
// Check if there's a saved theme in localStorage
|
||||
const savedTheme = localStorage.getItem("theme");
|
||||
if (savedTheme === "dark") {
|
||||
document.body.classList.add("dark-theme");
|
||||
themeToggleBtn.textContent = "🌞"; // Set the button to indicate light theme
|
||||
}
|
||||
}
|
||||
|
||||
// Run the theme toggle setup after each page load
|
||||
hook.afterEach(function (html) {
|
||||
// Make sure the theme toggle button is added after the page content changes
|
||||
if (!document.getElementById("theme-toggle")) {
|
||||
addThemeToggle();
|
||||
}
|
||||
});
|
||||
},
|
||||
],
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Documentation</title>
|
||||
|
||||
<!-- Docsify Vue Theme -->
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="//cdn.jsdelivr.net/npm/docsify@4/lib/themes/vue.css"
|
||||
/>
|
||||
|
||||
<!-- Link to your custom styles -->
|
||||
<link rel="stylesheet" href="./styles/style.css" />
|
||||
|
||||
<!-- Docsify Search Plugin (Make sure it's the correct version) -->
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://cdn.jsdelivr.net/npm/docsify-search@1.7.0/dist/docsify-search.min.css"
|
||||
/>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
<!-- Docsify Script -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/docsify/lib/docsify.min.js"></script>
|
||||
|
||||
<!-- Docsify Search Plugin Script -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/docsify/lib/plugins/search.min.js"></script>
|
||||
|
||||
<!-- Docsify Image Preview Plugin -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/docsify/lib/plugins/zoom-image.min.js"></script>
|
||||
|
||||
<!-- Docsify Theme Toggle Button and Search Integration -->
|
||||
<script>
|
||||
window.$docsify = {
|
||||
loadSidebar: true,
|
||||
loadNavbar: true,
|
||||
subMaxLevel: 2,
|
||||
auto2top: true,
|
||||
|
||||
search: {
|
||||
insertBefore: ".sidebar-nav", // Ensure the search bar is placed before the sidebar-nav element
|
||||
paths: "auto", // Automatically index all markdown files
|
||||
placeholder: "Search documentation...", // Placeholder text
|
||||
noData: "No results!", // Message when no results are found
|
||||
},
|
||||
|
||||
plugins: [
|
||||
function (hook, vm) {
|
||||
// Function to add the theme toggle button
|
||||
function addThemeToggle() {
|
||||
const themeButtonHtml = `
|
||||
<div class="theme-toggle-container">
|
||||
Theme :
|
||||
<button id="theme-toggle">🌙</button>
|
||||
</div>
|
||||
`;
|
||||
// Append the theme button inside the sidebar or navbar
|
||||
const sidebar = document.querySelector(".sidebar");
|
||||
if (sidebar) {
|
||||
sidebar.insertAdjacentHTML("beforebegin", themeButtonHtml);
|
||||
}
|
||||
|
||||
// Handle the theme toggle functionality
|
||||
const themeToggleBtn = document.getElementById("theme-toggle");
|
||||
themeToggleBtn.addEventListener("click", () => {
|
||||
// Toggle the dark-theme class
|
||||
const isDark = document.body.classList.toggle("dark-theme");
|
||||
themeToggleBtn.textContent = isDark ? "🌞" : "🌙";
|
||||
|
||||
// Optionally save the user's theme preference to localStorage
|
||||
localStorage.setItem("theme", isDark ? "dark" : "light");
|
||||
});
|
||||
|
||||
// Check if there's a saved theme in localStorage
|
||||
const savedTheme = localStorage.getItem("theme");
|
||||
if (savedTheme === "dark") {
|
||||
document.body.classList.add("dark-theme");
|
||||
themeToggleBtn.textContent = "🌞"; // Set the button to indicate light theme
|
||||
}
|
||||
}
|
||||
|
||||
// Run the theme toggle setup after each page load
|
||||
hook.afterEach(function (html) {
|
||||
// Make sure the theme toggle button is added after the page content changes
|
||||
if (!document.getElementById("theme-toggle")) {
|
||||
addThemeToggle();
|
||||
}
|
||||
});
|
||||
},
|
||||
],
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
server {
|
||||
listen 3000;
|
||||
server_name localhost;
|
||||
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
|
||||
location / {
|
||||
try_files $uri /index.html;
|
||||
}
|
||||
|
||||
# Redirect 404 errors to index.html (for React Router)
|
||||
error_page 404 /index.html;
|
||||
server {
|
||||
listen 3000;
|
||||
server_name localhost;
|
||||
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
|
||||
location / {
|
||||
try_files $uri /index.html;
|
||||
}
|
||||
|
||||
# Redirect 404 errors to index.html (for React Router)
|
||||
error_page 404 /index.html;
|
||||
}
|
||||
43477
app/package-lock.json
generated
146
app/package.json
@@ -1,71 +1,75 @@
|
||||
{
|
||||
"name": "dwinzo-app",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@react-three/csg": "^3.2.0",
|
||||
"@react-three/drei": "^9.113.0",
|
||||
"@react-three/fiber": "^8.17.7",
|
||||
"@react-three/postprocessing": "^2.16.3",
|
||||
"@testing-library/jest-dom": "^5.17.0",
|
||||
"@testing-library/react": "^13.4.0",
|
||||
"@testing-library/user-event": "^13.5.0",
|
||||
"@turf/turf": "^7.1.0",
|
||||
"@types/jest": "^27.5.2",
|
||||
"@types/react": "^18.3.5",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"@use-gesture/react": "^10.3.1",
|
||||
"chart.js": "^4.4.8",
|
||||
"glob": "^11.0.0",
|
||||
"gsap": "^3.12.5",
|
||||
"postprocessing": "^6.36.4",
|
||||
"prompt-sync": "^4.2.0",
|
||||
"react": "^18.3.1",
|
||||
"react-chartjs-2": "^5.3.0",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-router-dom": "^7.4.0",
|
||||
"react-scripts": "5.0.1",
|
||||
"react-toastify": "^10.0.5",
|
||||
"sass": "^1.78.0",
|
||||
"socket.io-client": "^4.8.1",
|
||||
"three": "^0.168.0",
|
||||
"typescript": "^4.9.5",
|
||||
"web-vitals": "^2.1.4",
|
||||
"zustand": "^5.0.0-rc.2"
|
||||
},
|
||||
"scripts": {
|
||||
"prepare": "husky",
|
||||
"prestart": "tsc scripts/git-prompt.ts && node scripts/git-prompt.js",
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
"test": "jest",
|
||||
"cypress:open": "cypress open",
|
||||
"cypress:run": "cypress run"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
"react-app",
|
||||
"react-app/jest"
|
||||
]
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.9.1",
|
||||
"@types/three": "^0.169.0",
|
||||
"cypress": "^13.14.2",
|
||||
"dotenv": "^16.4.5",
|
||||
"husky": "^9.1.6",
|
||||
"ts-node": "^10.9.2"
|
||||
}
|
||||
}
|
||||
{
|
||||
"name": "dwinzo-beta",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@dnd-kit/core": "^6.3.1",
|
||||
"@dnd-kit/sortable": "^10.0.0",
|
||||
"@react-three/csg": "^3.2.0",
|
||||
"@react-three/drei": "^9.113.0",
|
||||
"@react-three/fiber": "^8.17.7",
|
||||
"@react-three/postprocessing": "^2.16.3",
|
||||
"@testing-library/jest-dom": "^5.17.0",
|
||||
"@testing-library/react": "^13.4.0",
|
||||
"@testing-library/user-event": "^13.5.0",
|
||||
"@turf/turf": "^7.1.0",
|
||||
"@types/jest": "^27.5.2",
|
||||
"@types/react": "^18.3.5",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"@use-gesture/react": "^10.3.1",
|
||||
"chart.js": "^4.4.8",
|
||||
"glob": "^11.0.0",
|
||||
"gsap": "^3.12.5",
|
||||
"leva": "^0.10.0",
|
||||
"mqtt": "^5.10.4",
|
||||
"postprocessing": "^6.36.4",
|
||||
"prompt-sync": "^4.2.0",
|
||||
"react": "^18.3.1",
|
||||
"react-chartjs-2": "^5.3.0",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-router-dom": "^7.4.0",
|
||||
"react-scripts": "5.0.1",
|
||||
"react-toastify": "^10.0.5",
|
||||
"sass": "^1.78.0",
|
||||
"socket.io-client": "^4.8.1",
|
||||
"three": "^0.168.0",
|
||||
"typescript": "^4.9.5",
|
||||
"web-vitals": "^2.1.4",
|
||||
"zustand": "^5.0.0-rc.2"
|
||||
},
|
||||
"scripts": {
|
||||
"prepare": "husky",
|
||||
"prestart": "tsc scripts/git-prompt.ts && node scripts/git-prompt.js",
|
||||
"start": "react-scripts start",
|
||||
"build": "GENERATE_SOURCEMAP=false react-scripts build",
|
||||
"test": "jest",
|
||||
"cypress:open": "cypress open",
|
||||
"cypress:run": "cypress run"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
"react-app",
|
||||
"react-app/jest"
|
||||
]
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.9.1",
|
||||
"@types/three": "^0.169.0",
|
||||
"cypress": "^13.14.2",
|
||||
"dotenv": "^16.4.5",
|
||||
"husky": "^9.1.6",
|
||||
"ts-node": "^10.9.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,44 +1,44 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1", user-scalable=no" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Web site created using create-react-app"
|
||||
/>
|
||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
||||
<!--
|
||||
manifest.json provides metadata used when your web app is installed on a
|
||||
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||
-->
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||
<!--
|
||||
Notice the use of %PUBLIC_URL% in the tags above.
|
||||
It will be replaced with the URL of the `public` folder during the build.
|
||||
Only files inside the `public` folder can be referenced from the HTML.
|
||||
|
||||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
||||
work correctly both with client-side routing and a non-root public URL.
|
||||
Learn how to configure a non-root public URL by running `npm run build`.
|
||||
-->
|
||||
<title>Dwinzo (beta)</title>
|
||||
</head>
|
||||
<body data-theme="light">
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
<div id="root-over"></div>
|
||||
<!--
|
||||
This HTML file is a template.
|
||||
If you open it directly in the browser, you will see an empty page.
|
||||
|
||||
You can add webfonts, meta tags, or analytics to this file.
|
||||
The build step will place the bundled scripts into the <body> tag.
|
||||
|
||||
To begin the development, run `npm start` or `yarn start`.
|
||||
To create a production bundle, use `npm run build` or `yarn build`.
|
||||
-->
|
||||
</body>
|
||||
</html>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Web site created using create-react-app"
|
||||
/>
|
||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
||||
<!--
|
||||
manifest.json provides metadata used when your web app is installed on a
|
||||
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||
-->
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||
<!--
|
||||
Notice the use of %PUBLIC_URL% in the tags above.
|
||||
It will be replaced with the URL of the `public` folder during the build.
|
||||
Only files inside the `public` folder can be referenced from the HTML.
|
||||
|
||||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
||||
work correctly both with client-side routing and a non-root public URL.
|
||||
Learn how to configure a non-root public URL by running `npm run build`.
|
||||
-->
|
||||
<title>Dwinzo (beta)</title>
|
||||
</head>
|
||||
<body data-theme="light">
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
<div id="root-over"></div>
|
||||
<!--
|
||||
This HTML file is a template.
|
||||
If you open it directly in the browser, you will see an empty page.
|
||||
|
||||
You can add webfonts, meta tags, or analytics to this file.
|
||||
The build step will place the bundled scripts into the <body> tag.
|
||||
|
||||
To begin the development, run `npm start` or `yarn start`.
|
||||
To create a production bundle, use `npm run build` or `yarn build`.
|
||||
-->
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
{
|
||||
"short_name": "React App",
|
||||
"name": "Create React App Sample",
|
||||
"icons": [
|
||||
{
|
||||
"src": "favicon.ico",
|
||||
"sizes": "64x64 32x32 24x24 16x16",
|
||||
"type": "image/x-icon"
|
||||
},
|
||||
{
|
||||
"src": "logo192.png",
|
||||
"type": "image/png",
|
||||
"sizes": "192x192"
|
||||
},
|
||||
{
|
||||
"src": "logo512.png",
|
||||
"type": "image/png",
|
||||
"sizes": "512x512"
|
||||
}
|
||||
],
|
||||
"start_url": ".",
|
||||
"display": "standalone",
|
||||
"theme_color": "#000000",
|
||||
"background_color": "#ffffff"
|
||||
}
|
||||
{
|
||||
"short_name": "React App",
|
||||
"name": "Create React App Sample",
|
||||
"icons": [
|
||||
{
|
||||
"src": "favicon.ico",
|
||||
"sizes": "64x64 32x32 24x24 16x16",
|
||||
"type": "image/x-icon"
|
||||
},
|
||||
{
|
||||
"src": "logo192.png",
|
||||
"type": "image/png",
|
||||
"sizes": "192x192"
|
||||
},
|
||||
{
|
||||
"src": "logo512.png",
|
||||
"type": "image/png",
|
||||
"sizes": "512x512"
|
||||
}
|
||||
],
|
||||
"start_url": ".",
|
||||
"display": "standalone",
|
||||
"theme_color": "#000000",
|
||||
"background_color": "#ffffff"
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
# https://www.robotstxt.org/robotstxt.html
|
||||
User-agent: *
|
||||
Disallow:
|
||||
# https://www.robotstxt.org/robotstxt.html
|
||||
User-agent: *
|
||||
Disallow:
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import React from "react";
|
||||
import { render, screen } from "@testing-library/react";
|
||||
import App from "./app";
|
||||
|
||||
test("renders learn react link", () => {
|
||||
render(<App />);
|
||||
const linkElement = screen.getByText(/learn react/i);
|
||||
expect(linkElement).toBeInTheDocument();
|
||||
});
|
||||
import React from "react";
|
||||
import { render, screen } from "@testing-library/react";
|
||||
import App from "./app";
|
||||
|
||||
test("renders learn react link", () => {
|
||||
render(<App />);
|
||||
const linkElement = screen.getByText(/learn react/i);
|
||||
expect(linkElement).toBeInTheDocument();
|
||||
});
|
||||
|
||||
@@ -1,26 +1,26 @@
|
||||
import React from "react";
|
||||
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
|
||||
import Dashboard from "./pages/Dashboard";
|
||||
import Project from "./pages/Project";
|
||||
import UserAuth from "./pages/UserAuth";
|
||||
import ToastProvider from "./components/templates/ToastProvider";
|
||||
import "./styles/main.scss"
|
||||
|
||||
const App: React.FC = () => {
|
||||
return (
|
||||
<ToastProvider>
|
||||
<Router>
|
||||
<Routes>
|
||||
<Route
|
||||
path="/"
|
||||
element={<UserAuth />}
|
||||
/>
|
||||
<Route path="/dashboard" element={<Dashboard />} />
|
||||
<Route path="/project" element={<Project />} />
|
||||
</Routes>
|
||||
</Router>
|
||||
</ToastProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export default App;
|
||||
import React from "react";
|
||||
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
|
||||
import Dashboard from "./pages/Dashboard";
|
||||
import Project from "./pages/Project";
|
||||
import UserAuth from "./pages/UserAuth";
|
||||
import ToastProvider from "./components/templates/ToastProvider";
|
||||
import "./styles/main.scss"
|
||||
|
||||
const App: React.FC = () => {
|
||||
return (
|
||||
<ToastProvider>
|
||||
<Router>
|
||||
<Routes>
|
||||
<Route
|
||||
path="/"
|
||||
element={<UserAuth />}
|
||||
/>
|
||||
<Route path="/dashboard" element={<Dashboard />} />
|
||||
<Route path="/project" element={<Project />} />
|
||||
</Routes>
|
||||
</Router>
|
||||
</ToastProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export default App;
|
||||
|
||||
BIN
app/src/assets/floor/concreteFloorWorn001Diff2k.jpg
Normal file
|
After Width: | Height: | Size: 472 KiB |
BIN
app/src/assets/floor/concreteFloorWorn001NorGl2k.jpg
Normal file
|
After Width: | Height: | Size: 850 KiB |
BIN
app/src/assets/gltf-glb/arch.glb
Normal file
121
app/src/assets/gltf-glb/camera face 2.gltf
Normal file
BIN
app/src/assets/gltf-glb/crate_box.glb
Normal file
BIN
app/src/assets/gltf-glb/door.glb
Normal file
BIN
app/src/assets/gltf-glb/window.glb
Normal file
BIN
app/src/assets/hdr/mudroadpuresky2k.hdr
Normal file
BIN
app/src/assets/image/image.png
Normal file
|
After Width: | Height: | Size: 35 KiB |
BIN
app/src/assets/image/userImage.png
Normal file
|
After Width: | Height: | Size: 480 KiB |
BIN
app/src/assets/textures/floor/concreteFloorWorn001Diff2k.jpg
Normal file
|
After Width: | Height: | Size: 472 KiB |
BIN
app/src/assets/textures/floor/concreteFloorWorn001NorGl2k.jpg
Normal file
|
After Width: | Height: | Size: 850 KiB |
BIN
app/src/assets/textures/hdr/mudroadpuresky2k.hdr
Normal file
@@ -1,160 +1,160 @@
|
||||
export function CleanPannel() {
|
||||
return (
|
||||
<svg
|
||||
width="12"
|
||||
height="12"
|
||||
viewBox="0 0 12 12"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<g clipPath="url(#clip0_1782_1158)">
|
||||
<path d="M12 0H0V12H12V0Z" fill="white" fillOpacity="0.01" />
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M5 1.47852H7V3.47853H10.75V5.47853H1.25V3.47853H5V1.47852Z"
|
||||
stroke="#2B3344"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path d="M2 10H10V5.5H2V10Z" stroke="#2B3344" strokeLinejoin="round" />
|
||||
<path
|
||||
d="M4 9.97439V8.47852"
|
||||
stroke="#2B3344"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M6 9.97461V8.47461"
|
||||
stroke="#2B3344"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M8 9.97439V8.47852"
|
||||
stroke="#2B3344"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M3 10H9"
|
||||
stroke="#2B3344"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_1782_1158">
|
||||
<rect width="12" height="12" fill="white" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function EyeIcon({ fill }: { fill?: string }) {
|
||||
return (
|
||||
<svg
|
||||
width="14"
|
||||
height="15"
|
||||
viewBox="0 0 14 15"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M8.75047 7.4375C8.75047 8.40402 7.967 9.1875 7.00047 9.1875C6.034 9.1875 5.25049 8.40402 5.25049 7.4375C5.25049 6.47097 6.034 5.6875 7.00047 5.6875C7.967 5.6875 8.75047 6.47097 8.75047 7.4375Z"
|
||||
stroke={fill}
|
||||
strokeOpacity="1"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M7.00086 3.35419C4.3889 3.35419 2.1779 5.07087 1.43457 7.43752C2.17789 9.80416 4.3889 11.5209 7.00086 11.5209C9.6128 11.5209 11.8238 9.80416 12.5671 7.43752C11.8238 5.07088 9.6128 3.35419 7.00086 3.35419Z"
|
||||
stroke={fill}
|
||||
strokeOpacity="1"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function LockIcon({ fill }: { fill?: string }) {
|
||||
return (
|
||||
<svg
|
||||
width="14"
|
||||
height="15"
|
||||
viewBox="0 0 14 15"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M4.0835 6.28763C4.35849 6.27083 4.69751 6.27083 5.1335 6.27083H8.86683C9.30281 6.27083 9.64185 6.27083 9.91683 6.28763M4.0835 6.28763C3.74031 6.30857 3.49683 6.35571 3.28901 6.46158C2.95973 6.62935 2.69201 6.89704 2.52423 7.22633C2.3335 7.60072 2.3335 8.09072 2.3335 9.07083V9.8875C2.3335 10.8676 2.3335 11.3576 2.52423 11.732C2.69201 12.0613 2.95973 12.329 3.28901 12.4967C3.66336 12.6875 4.1534 12.6875 5.1335 12.6875H8.86683C9.84695 12.6875 10.3369 12.6875 10.7113 12.4967C11.0406 12.329 11.3083 12.0613 11.4761 11.732C11.6668 11.3576 11.6668 10.8676 11.6668 9.8875V9.07083C11.6668 8.09072 11.6668 7.60072 11.4761 7.22633C11.3083 6.89704 11.0406 6.62935 10.7113 6.46158C10.5035 6.35571 10.26 6.30857 9.91683 6.28763M4.0835 6.28763V5.10417C4.0835 3.49334 5.38933 2.1875 7.00016 2.1875C8.61098 2.1875 9.91683 3.49334 9.91683 5.10417V6.28763"
|
||||
stroke={fill}
|
||||
strokeOpacity="1"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
export function StockIncreseIcon() {
|
||||
return (
|
||||
<svg
|
||||
width="9"
|
||||
height="9"
|
||||
viewBox="0 0 9 9"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<g clip-path="url(#clip0_3050_69519)">
|
||||
<path
|
||||
d="M7.80766 6.99219H1.17811C0.752382 6.99219 0.407227 7.33734 0.407227 7.76307C0.407227 8.18879 0.752382 8.53395 1.17811 8.53395H7.80766C8.23339 8.53395 8.57854 8.18879 8.57854 7.76307C8.57854 7.33733 8.23339 6.99219 7.80766 6.99219Z"
|
||||
fill="white"
|
||||
/>
|
||||
<path
|
||||
d="M2.05066 6.50215C2.47639 6.50215 2.82154 6.15699 2.82154 5.73127V2.7865C2.82154 2.36078 2.47639 2.01562 2.05066 2.01562C1.62494 2.01562 1.27979 2.36078 1.27979 2.7865V5.73127C1.27977 6.15699 1.62494 6.50215 2.05066 6.50215Z"
|
||||
fill="white"
|
||||
/>
|
||||
<path
|
||||
d="M4.49598 6.49421C4.9217 6.49421 5.26686 6.14905 5.26686 5.72333V1.80213C5.26686 1.37641 4.9217 1.03125 4.49598 1.03125C4.07025 1.03125 3.7251 1.37641 3.7251 1.80213V5.72333C3.7251 6.14905 4.07023 6.49421 4.49598 6.49421Z"
|
||||
fill="white"
|
||||
/>
|
||||
<path
|
||||
d="M6.92957 6.50192C7.35529 6.50192 7.70042 6.15677 7.70042 5.73104V0.83338C7.70046 0.407655 7.35532 0.0625 6.92957 0.0625C6.50385 0.0625 6.15869 0.407655 6.15869 0.83338V5.73103C6.15869 6.15677 6.50385 6.50192 6.92957 6.50192Z"
|
||||
fill="white"
|
||||
/>
|
||||
</g>
|
||||
<rect
|
||||
x="0.27293"
|
||||
y="0.066387"
|
||||
width="8.45313"
|
||||
height="8.45313"
|
||||
stroke="url(#paint0_linear_3050_69519)"
|
||||
strokeWidth="0.0233989"
|
||||
/>
|
||||
<defs>
|
||||
<linearGradient
|
||||
id="paint0_linear_3050_69519"
|
||||
x1="4.4995"
|
||||
y1="0.0546875"
|
||||
x2="4.4995"
|
||||
y2="8.53122"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stop-color="#31B2B9" />
|
||||
<stop offset="1" stop-color="#FBD8B8" />
|
||||
</linearGradient>
|
||||
<clipPath id="clip0_3050_69519">
|
||||
<rect
|
||||
x="0.26123"
|
||||
y="0.0546875"
|
||||
width="8.47653"
|
||||
height="8.47653"
|
||||
fill="white"
|
||||
/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
export function CleanPannel() {
|
||||
return (
|
||||
<svg
|
||||
width="12"
|
||||
height="12"
|
||||
viewBox="0 0 12 12"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<g clipPath="url(#clip0_1782_1158)">
|
||||
<path d="M12 0H0V12H12V0Z" fill="white" fillOpacity="0.01" />
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M5 1.47852H7V3.47853H10.75V5.47853H1.25V3.47853H5V1.47852Z"
|
||||
stroke="#2B3344"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path d="M2 10H10V5.5H2V10Z" stroke="#2B3344" strokeLinejoin="round" />
|
||||
<path
|
||||
d="M4 9.97439V8.47852"
|
||||
stroke="#2B3344"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M6 9.97461V8.47461"
|
||||
stroke="#2B3344"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M8 9.97439V8.47852"
|
||||
stroke="#2B3344"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M3 10H9"
|
||||
stroke="#2B3344"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_1782_1158">
|
||||
<rect width="12" height="12" fill="white" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function EyeIcon({ fill }: { fill?: string }) {
|
||||
return (
|
||||
<svg
|
||||
width="14"
|
||||
height="15"
|
||||
viewBox="0 0 14 15"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M8.75047 7.4375C8.75047 8.40402 7.967 9.1875 7.00047 9.1875C6.034 9.1875 5.25049 8.40402 5.25049 7.4375C5.25049 6.47097 6.034 5.6875 7.00047 5.6875C7.967 5.6875 8.75047 6.47097 8.75047 7.4375Z"
|
||||
stroke={fill}
|
||||
strokeOpacity="1"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M7.00086 3.35419C4.3889 3.35419 2.1779 5.07087 1.43457 7.43752C2.17789 9.80416 4.3889 11.5209 7.00086 11.5209C9.6128 11.5209 11.8238 9.80416 12.5671 7.43752C11.8238 5.07088 9.6128 3.35419 7.00086 3.35419Z"
|
||||
stroke={fill}
|
||||
strokeOpacity="1"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function LockIcon({ fill }: { fill?: string }) {
|
||||
return (
|
||||
<svg
|
||||
width="14"
|
||||
height="15"
|
||||
viewBox="0 0 14 15"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M4.0835 6.28763C4.35849 6.27083 4.69751 6.27083 5.1335 6.27083H8.86683C9.30281 6.27083 9.64185 6.27083 9.91683 6.28763M4.0835 6.28763C3.74031 6.30857 3.49683 6.35571 3.28901 6.46158C2.95973 6.62935 2.69201 6.89704 2.52423 7.22633C2.3335 7.60072 2.3335 8.09072 2.3335 9.07083V9.8875C2.3335 10.8676 2.3335 11.3576 2.52423 11.732C2.69201 12.0613 2.95973 12.329 3.28901 12.4967C3.66336 12.6875 4.1534 12.6875 5.1335 12.6875H8.86683C9.84695 12.6875 10.3369 12.6875 10.7113 12.4967C11.0406 12.329 11.3083 12.0613 11.4761 11.732C11.6668 11.3576 11.6668 10.8676 11.6668 9.8875V9.07083C11.6668 8.09072 11.6668 7.60072 11.4761 7.22633C11.3083 6.89704 11.0406 6.62935 10.7113 6.46158C10.5035 6.35571 10.26 6.30857 9.91683 6.28763M4.0835 6.28763V5.10417C4.0835 3.49334 5.38933 2.1875 7.00016 2.1875C8.61098 2.1875 9.91683 3.49334 9.91683 5.10417V6.28763"
|
||||
stroke={fill}
|
||||
strokeOpacity="1"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
export function StockIncreseIcon() {
|
||||
return (
|
||||
<svg
|
||||
width="9"
|
||||
height="9"
|
||||
viewBox="0 0 9 9"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<g clipPath="url(#clip0_3050_69519)">
|
||||
<path
|
||||
d="M7.80766 6.99219H1.17811C0.752382 6.99219 0.407227 7.33734 0.407227 7.76307C0.407227 8.18879 0.752382 8.53395 1.17811 8.53395H7.80766C8.23339 8.53395 8.57854 8.18879 8.57854 7.76307C8.57854 7.33733 8.23339 6.99219 7.80766 6.99219Z"
|
||||
fill="white"
|
||||
/>
|
||||
<path
|
||||
d="M2.05066 6.50215C2.47639 6.50215 2.82154 6.15699 2.82154 5.73127V2.7865C2.82154 2.36078 2.47639 2.01562 2.05066 2.01562C1.62494 2.01562 1.27979 2.36078 1.27979 2.7865V5.73127C1.27977 6.15699 1.62494 6.50215 2.05066 6.50215Z"
|
||||
fill="white"
|
||||
/>
|
||||
<path
|
||||
d="M4.49598 6.49421C4.9217 6.49421 5.26686 6.14905 5.26686 5.72333V1.80213C5.26686 1.37641 4.9217 1.03125 4.49598 1.03125C4.07025 1.03125 3.7251 1.37641 3.7251 1.80213V5.72333C3.7251 6.14905 4.07023 6.49421 4.49598 6.49421Z"
|
||||
fill="white"
|
||||
/>
|
||||
<path
|
||||
d="M6.92957 6.50192C7.35529 6.50192 7.70042 6.15677 7.70042 5.73104V0.83338C7.70046 0.407655 7.35532 0.0625 6.92957 0.0625C6.50385 0.0625 6.15869 0.407655 6.15869 0.83338V5.73103C6.15869 6.15677 6.50385 6.50192 6.92957 6.50192Z"
|
||||
fill="white"
|
||||
/>
|
||||
</g>
|
||||
<rect
|
||||
x="0.27293"
|
||||
y="0.066387"
|
||||
width="8.45313"
|
||||
height="8.45313"
|
||||
stroke="url(#paint0_linear_3050_69519)"
|
||||
strokeWidth="0.0233989"
|
||||
/>
|
||||
<defs>
|
||||
<linearGradient
|
||||
id="paint0_linear_3050_69519"
|
||||
x1="4.4995"
|
||||
y1="0.0546875"
|
||||
x2="4.4995"
|
||||
y2="8.53122"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#31B2B9" />
|
||||
<stop offset="1" stopColor="#FBD8B8" />
|
||||
</linearGradient>
|
||||
<clipPath id="clip0_3050_69519">
|
||||
<rect
|
||||
x="0.26123"
|
||||
y="0.0546875"
|
||||
width="8.47653"
|
||||
height="8.47653"
|
||||
fill="white"
|
||||
/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
132
app/src/components/icons/marketPlaceIcons.tsx
Normal file
@@ -0,0 +1,132 @@
|
||||
export function StarsIcon() {
|
||||
return (
|
||||
<svg
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M9.39075 3.67726C9.58367 3.24444 9.68017 3.02804 9.8145 2.96139C9.93117 2.90349 10.0682 2.90349 10.1848 2.96139C10.3192 3.02804 10.4157 3.24444 10.6086 3.67726L12.1453 7.1247C12.2023 7.25265 12.2308 7.31663 12.275 7.36562C12.314 7.40897 12.3618 7.44364 12.415 7.46738C12.4753 7.49421 12.5449 7.50156 12.6843 7.51626L16.4378 7.91244C16.9091 7.96217 17.1447 7.98704 17.2496 8.09419C17.3407 8.18727 17.383 8.3176 17.364 8.44647C17.3422 8.5948 17.1662 8.75339 16.8142 9.07064L14.0103 11.5975C13.9063 11.6912 13.8542 11.7381 13.8213 11.7952C13.7921 11.8458 13.7738 11.9019 13.7678 11.9599C13.7608 12.0255 13.7753 12.094 13.8044 12.2311L14.5876 15.9233C14.6859 16.3869 14.7351 16.6186 14.6656 16.7515C14.6052 16.867 14.4943 16.9475 14.3659 16.9692C14.2181 16.9942 14.0128 16.8759 13.6023 16.6391L10.3328 14.7533C10.2114 14.6833 10.1508 14.6484 10.0863 14.6346C10.0292 14.6226 9.97017 14.6226 9.91309 14.6346C9.84859 14.6484 9.78792 14.6833 9.66659 14.7533L6.39702 16.6391C5.98654 16.8759 5.7813 16.9942 5.63345 16.9692C5.50503 16.9475 5.39416 16.867 5.3338 16.7515C5.2643 16.6186 5.31346 16.3869 5.41179 15.9233L6.19492 12.2311C6.22399 12.094 6.23852 12.0255 6.23162 11.9599C6.22551 11.9019 6.20729 11.8458 6.17812 11.7952C6.14516 11.7381 6.09313 11.6912 5.98907 11.5975L3.18522 9.07064C2.83321 8.75339 2.6572 8.5948 2.63532 8.44647C2.61632 8.3176 2.65866 8.18727 2.74978 8.09419C2.85468 7.98704 3.0903 7.96217 3.56155 7.91244L7.31513 7.51626C7.45445 7.50156 7.5241 7.49421 7.58433 7.46738C7.63762 7.44364 7.68534 7.40897 7.72439 7.36562C7.76851 7.31663 7.79703 7.25265 7.85407 7.1247L9.39075 3.67726Z"
|
||||
stroke="#595965"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
export function DownloadIcon() {
|
||||
return (
|
||||
<svg
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M2.5 11.875C2.84518 11.875 3.125 12.1548 3.125 12.5C3.125 13.6962 3.12633 14.5304 3.21096 15.1599C3.29317 15.7714 3.44354 16.0952 3.67418 16.3258C3.90481 16.5565 4.22863 16.7068 4.8401 16.7891C5.46956 16.8737 6.30383 16.875 7.5 16.875H12.5C13.6962 16.875 14.5304 16.8737 15.1599 16.7891C15.7714 16.7068 16.0952 16.5565 16.3258 16.3258C16.5565 16.0952 16.7068 15.7714 16.7891 15.1599C16.8737 14.5304 16.875 13.6962 16.875 12.5C16.875 12.1548 17.1548 11.875 17.5 11.875C17.8452 11.875 18.125 12.1548 18.125 12.5V12.5458C18.125 13.6854 18.125 14.604 18.0279 15.3265C17.9271 16.0766 17.7113 16.7081 17.2097 17.2097C16.7081 17.7113 16.0766 17.9271 15.3265 18.0279C14.604 18.125 13.6854 18.125 12.5458 18.125H7.45428C6.31462 18.125 5.39602 18.125 4.67354 18.0279C3.92345 17.9271 3.29189 17.7113 2.79029 17.2097C2.28869 16.7081 2.07295 16.0766 1.9721 15.3265C1.87497 14.604 1.87498 13.6854 1.875 12.5458C1.875 12.5305 1.875 12.5152 1.875 12.5C1.875 12.1548 2.15483 11.875 2.5 11.875Z"
|
||||
fill="#FCFDFD"
|
||||
/>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M10.0003 13.9583C10.1758 13.9583 10.3432 13.8846 10.4616 13.7551L13.7949 10.1093C14.0278 9.8545 14.0102 9.45917 13.7554 9.22625C13.5007 8.99333 13.1053 9.011 12.8724 9.26575L10.6253 11.7235V2.5C10.6253 2.15483 10.3455 1.875 10.0003 1.875C9.65516 1.875 9.37533 2.15483 9.37533 2.5V11.7235L7.12827 9.26575C6.89535 9.011 6.50002 8.99333 6.24527 9.22625C5.99052 9.45917 5.97281 9.8545 6.20573 10.1093L9.53908 13.7551C9.65749 13.8846 9.82483 13.9583 10.0003 13.9583Z"
|
||||
fill="#FCFDFD"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function EyeIconBig() {
|
||||
return (
|
||||
<svg
|
||||
width="22"
|
||||
height="22"
|
||||
viewBox="0 0 22 22"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M2.75 11.918C6.05 4.58464 15.95 4.58464 19.25 11.918"
|
||||
stroke="#2B3344"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M11 15.5859C10.6389 15.5859 10.2813 15.5148 9.94762 15.3766C9.61398 15.2384 9.31082 15.0358 9.05546 14.7805C8.80009 14.5251 8.59753 14.222 8.45933 13.8883C8.32113 13.5547 8.25 13.1971 8.25 12.8359C8.25 12.4748 8.32113 12.1172 8.45933 11.7836C8.59753 11.4499 8.80009 11.1468 9.05546 10.8914C9.31082 10.636 9.61398 10.4335 9.94762 10.2953C10.2813 10.1571 10.6389 10.0859 11 10.0859C11.7293 10.0859 12.4288 10.3757 12.9445 10.8914C13.4603 11.4071 13.75 12.1066 13.75 12.8359C13.75 13.5653 13.4603 14.2648 12.9445 14.7805C12.4288 15.2962 11.7293 15.5859 11 15.5859Z"
|
||||
stroke="#2B3344"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function CommentsIcon() {
|
||||
return (
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<g clip-path="url(#clip0_2926_17620)">
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M8 13C7.416 13 6.852 12.932 6.31 12.8165L3.956 14.2315L3.9875 11.912C2.183 10.827 1 9.033 1 7C1 3.6865 4.134 1 8 1C11.866 1 15 3.6865 15 7C15 10.314 11.866 13 8 13ZM8 0C3.582 0 0 3.1345 0 7C0 9.2095 1.1725 11.177 3 12.4595V16L6.5045 13.8735C6.9895 13.9535 7.4885 14 8 14C12.418 14 16 10.866 16 7C16 3.1345 12.418 0 8 0ZM11.5 5.5H4.5C4.224 5.5 4 5.724 4 6C4 6.2765 4.224 6.5 4.5 6.5H11.5C11.776 6.5 12 6.2765 12 6C12 5.724 11.776 5.5 11.5 5.5ZM10.5 8.5H5.5C5.224 8.5 5 8.7235 5 9C5 9.2765 5.224 9.5 5.5 9.5H10.5C10.776 9.5 11 9.2765 11 9C11 8.7235 10.776 8.5 10.5 8.5Z"
|
||||
fill="#2B3344"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_2926_17620">
|
||||
<rect width="16" height="16" fill="white" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function VerifiedIcon() {
|
||||
return (
|
||||
<svg
|
||||
width="12"
|
||||
height="13"
|
||||
viewBox="0 0 12 13"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M4.7962 2.10014C4.67444 2.2039 4.61356 2.25579 4.54853 2.29937C4.39948 2.39927 4.23209 2.46861 4.05605 2.50336C3.97926 2.51853 3.89952 2.52489 3.74004 2.53761C3.33935 2.56959 3.139 2.58558 2.97186 2.64462C2.58526 2.78117 2.28117 3.08526 2.14462 3.47186C2.08558 3.639 2.06959 3.83935 2.03761 4.24004C2.02489 4.39952 2.01853 4.47926 2.00336 4.55605C1.96861 4.73209 1.89927 4.89948 1.79937 5.04853C1.75579 5.11356 1.70391 5.17444 1.60014 5.2962C1.33942 5.60215 1.20905 5.7551 1.13261 5.91505C0.955796 6.285 0.955796 6.715 1.13261 7.08495C1.20906 7.2449 1.33942 7.39785 1.60014 7.7038C1.70389 7.82555 1.75579 7.88645 1.79937 7.95145C1.89927 8.1005 1.96861 8.2679 2.00336 8.44395C2.01853 8.52075 2.02489 8.6005 2.03761 8.75995C2.06959 9.16065 2.08558 9.361 2.14462 9.52815C2.28117 9.91475 2.58526 10.2189 2.97186 10.3554C3.139 10.4144 3.33935 10.4304 3.74004 10.4624C3.89952 10.4751 3.97926 10.4815 4.05605 10.4966C4.23209 10.5314 4.39948 10.6007 4.54853 10.7007C4.61356 10.7442 4.67444 10.7961 4.7962 10.8998C5.10215 11.1606 5.2551 11.2909 5.41505 11.3674C5.785 11.5442 6.215 11.5442 6.58495 11.3674C6.7449 11.2909 6.89785 11.1606 7.2038 10.8998C7.32555 10.7961 7.38645 10.7442 7.45145 10.7007C7.6005 10.6007 7.7679 10.5314 7.94395 10.4966C8.02075 10.4815 8.1005 10.4751 8.25995 10.4624C8.66065 10.4304 8.861 10.4144 9.02815 10.3554C9.41475 10.2189 9.71885 9.91475 9.8554 9.52815C9.9144 9.361 9.9304 9.16065 9.9624 8.75995C9.9751 8.6005 9.9815 8.52075 9.99665 8.44395C10.0314 8.2679 10.1007 8.1005 10.2007 7.95145C10.2442 7.88645 10.2961 7.82555 10.3998 7.7038C10.6606 7.39785 10.7909 7.2449 10.8674 7.08495C11.0442 6.715 11.0442 6.285 10.8674 5.91505C10.7909 5.7551 10.6606 5.60215 10.3998 5.2962C10.2961 5.17444 10.2442 5.11356 10.2007 5.04853C10.1007 4.89948 10.0314 4.73209 9.99665 4.55605C9.9815 4.47926 9.9751 4.39952 9.9624 4.24004C9.9304 3.83935 9.9144 3.639 9.8554 3.47186C9.71885 3.08526 9.41475 2.78117 9.02815 2.64462C8.861 2.58558 8.66065 2.56959 8.25995 2.53761C8.1005 2.52489 8.02075 2.51853 7.94395 2.50336C7.7679 2.46861 7.6005 2.39927 7.45145 2.29937C7.38645 2.25579 7.32555 2.20391 7.2038 2.10014C6.89785 1.83942 6.7449 1.70906 6.58495 1.63261C6.215 1.4558 5.785 1.4558 5.41505 1.63261C5.2551 1.70905 5.10215 1.83942 4.7962 2.10014ZM8.18675 5.43157C8.34565 5.27265 8.34565 5.015 8.18675 4.85608C8.02785 4.69717 7.77015 4.69717 7.61125 4.85608L5.18615 7.2812L4.38873 6.4838C4.22982 6.3249 3.97216 6.3249 3.81325 6.4838C3.65433 6.6427 3.65433 6.90035 3.81325 7.0593L4.89839 8.14445C5.0573 8.30335 5.31495 8.30335 5.4739 8.14445L8.18675 5.43157Z"
|
||||
fill="#6F42C1"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function StarsIconSmall() {
|
||||
return (
|
||||
<svg
|
||||
width="13"
|
||||
height="13"
|
||||
viewBox="0 0 13 13"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M6.43387 2.70558C6.54962 2.44589 6.60752 2.31604 6.68812 2.27606C6.75812 2.24131 6.84032 2.24131 6.91032 2.27606C6.99092 2.31604 7.04882 2.44589 7.16457 2.70558L8.08657 4.77404C8.12082 4.85081 8.13792 4.8892 8.16442 4.91859C8.18782 4.9446 8.21647 4.96541 8.24842 4.97965C8.28457 4.99575 8.32637 5.00016 8.40997 5.00898L10.6621 5.24668C10.9449 5.27652 11.0862 5.29144 11.1492 5.35574C11.2038 5.41158 11.2292 5.48978 11.2178 5.5671C11.2047 5.6561 11.0991 5.75125 10.8879 5.9416L9.20557 7.4577C9.14317 7.51395 9.11192 7.5421 9.09217 7.57635C9.07467 7.6067 9.06372 7.64035 9.06007 7.67515C9.05592 7.7145 9.06462 7.7556 9.08207 7.83785L9.55197 10.0532C9.61097 10.3314 9.64047 10.4704 9.59877 10.5501C9.56252 10.6194 9.49602 10.6677 9.41897 10.6808C9.33027 10.6958 9.20712 10.6248 8.96082 10.4827L6.99907 9.3512C6.92627 9.3092 6.88987 9.28825 6.85117 9.28C6.81692 9.27275 6.78152 9.27275 6.74727 9.28C6.70857 9.28825 6.67217 9.3092 6.59937 9.3512L4.63763 10.4827C4.39134 10.6248 4.26819 10.6958 4.17949 10.6808C4.10243 10.6677 4.03591 10.6194 3.99969 10.5501C3.958 10.4704 3.98749 10.3314 4.04649 10.0532L4.51637 7.83785C4.53381 7.7556 4.54253 7.7145 4.53839 7.67515C4.53472 7.64035 4.52379 7.6067 4.50629 7.57635C4.48651 7.5421 4.45529 7.51395 4.39286 7.4577L2.71055 5.9416C2.49934 5.75125 2.39374 5.6561 2.38061 5.5671C2.36921 5.48978 2.39461 5.41158 2.44928 5.35574C2.51222 5.29144 2.6536 5.27652 2.93635 5.24668L5.18849 5.00898C5.27208 5.00016 5.31387 4.99575 5.35001 4.97965C5.38199 4.96541 5.41062 4.9446 5.43405 4.91859C5.46052 4.8892 5.47763 4.85081 5.51186 4.77404L6.43387 2.70558Z"
|
||||
stroke="#595965"
|
||||
stroke-width="0.6"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@@ -3,9 +3,12 @@ import { ToggleSidebarIcon } from "../../icons/HeaderIcons";
|
||||
import { LogoIcon } from "../../icons/Logo";
|
||||
import FileMenu from "../../ui/FileMenu";
|
||||
import useToggleStore from "../../../store/useUIToggleStore";
|
||||
import useModuleStore from "../../../store/useModuleStore";
|
||||
|
||||
const Header: React.FC = () => {
|
||||
const { toggleUI, setToggleUI } = useToggleStore();
|
||||
const { activeModule } = useModuleStore();
|
||||
|
||||
return (
|
||||
<div className="header-container">
|
||||
<div className="header-content">
|
||||
@@ -19,7 +22,7 @@ const Header: React.FC = () => {
|
||||
<div
|
||||
className={`toggle-sidebar-ui-button ${!toggleUI ? "active" : ""}`}
|
||||
onClick={() => {
|
||||
setToggleUI(!toggleUI);
|
||||
if (activeModule !== "market") setToggleUI(!toggleUI);
|
||||
}}
|
||||
>
|
||||
<ToggleSidebarIcon />
|
||||
|
||||
@@ -9,7 +9,6 @@ const chartTypes: ChartType[] = [
|
||||
"line",
|
||||
"pie",
|
||||
"doughnut",
|
||||
"radar",
|
||||
"polarArea",
|
||||
];
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import Header from "./Header";
|
||||
import useModuleStore from "../../../store/useModuleStore";
|
||||
import useModuleStore, {
|
||||
useSubModuleStore,
|
||||
} from "../../../store/useModuleStore";
|
||||
import {
|
||||
AnalysisIcon,
|
||||
MechanicsIcon,
|
||||
@@ -14,15 +16,19 @@ import GlobalProperties from "./properties/GlobalProperties";
|
||||
import AsstePropertiies from "./properties/AssetProperties";
|
||||
import Analysis from "./analysis/Analysis";
|
||||
import Simulations from "./simulation/Simulations";
|
||||
import { useSelectedActionSphere } from "../../../store/store";
|
||||
import ZoneProperties from "./properties/ZoneProperties";
|
||||
|
||||
const SideBarRight: React.FC = () => {
|
||||
const { activeModule } = useModuleStore();
|
||||
const [activeList, setActiveList] = useState("properties");
|
||||
const [activeList] = useState("properties");
|
||||
const { toggleUI } = useToggleStore();
|
||||
|
||||
const { selectedActionSphere } = useSelectedActionSphere();
|
||||
const { subModule, setSubModule } = useSubModuleStore();
|
||||
// Reset activeList whenever activeModule changes
|
||||
useEffect(() => {
|
||||
setActiveList("properties");
|
||||
if (activeModule !== "simulation") setSubModule("properties");
|
||||
if (activeModule === "simulation") setSubModule("mechanics");
|
||||
}, [activeModule]);
|
||||
|
||||
return (
|
||||
@@ -30,37 +36,39 @@ const SideBarRight: React.FC = () => {
|
||||
<Header />
|
||||
{toggleUI && (
|
||||
<div className="sidebar-actions-container">
|
||||
{/* {activeModule === "builder" && ( */}
|
||||
<div
|
||||
className={`sidebar-action-list ${
|
||||
activeList === "properties" ? "active" : ""
|
||||
subModule === "properties" ? "active" : ""
|
||||
}`}
|
||||
onClick={() => setActiveList("properties")}
|
||||
onClick={() => setSubModule("properties")}
|
||||
>
|
||||
<PropertiesIcon isActive={activeList === "properties"} />
|
||||
<PropertiesIcon isActive={subModule === "properties"} />
|
||||
</div>
|
||||
{/* )} */}
|
||||
{activeModule === "simulation" && (
|
||||
<>
|
||||
<div
|
||||
className={`sidebar-action-list ${
|
||||
activeList === "mechanics" ? "active" : ""
|
||||
subModule === "mechanics" ? "active" : ""
|
||||
}`}
|
||||
onClick={() => setActiveList("mechanics")}
|
||||
onClick={() => setSubModule("mechanics")}
|
||||
>
|
||||
<MechanicsIcon isActive={activeList === "mechanics"} />
|
||||
</div>
|
||||
<div
|
||||
className={`sidebar-action-list ${
|
||||
activeList === "simulations" ? "active" : ""
|
||||
subModule === "simulations" ? "active" : ""
|
||||
}`}
|
||||
onClick={() => setActiveList("simulations")}
|
||||
onClick={() => setSubModule("simulations")}
|
||||
>
|
||||
<SimulationIcon isActive={activeList === "simulations"} />
|
||||
</div>
|
||||
<div
|
||||
className={`sidebar-action-list ${
|
||||
activeList === "analysis" ? "active" : ""
|
||||
subModule === "analysis" ? "active" : ""
|
||||
}`}
|
||||
onClick={() => setActiveList("analysis")}
|
||||
onClick={() => setSubModule("analysis")}
|
||||
>
|
||||
<AnalysisIcon isActive={activeList === "analysis"} />
|
||||
</div>
|
||||
@@ -75,6 +83,7 @@ const SideBarRight: React.FC = () => {
|
||||
<div className="sidebar-right-container">
|
||||
<div className="sidebar-right-content-container">
|
||||
<GlobalProperties />
|
||||
{/* <ZoneProperties /> */}
|
||||
{/* <AsstePropertiies /> */}
|
||||
</div>
|
||||
</div>
|
||||
@@ -84,14 +93,21 @@ const SideBarRight: React.FC = () => {
|
||||
|
||||
{toggleUI && activeModule === "simulation" && (
|
||||
<>
|
||||
{activeList === "mechanics" && (
|
||||
{subModule === "mechanics" && selectedActionSphere && (
|
||||
<div className="sidebar-right-container">
|
||||
<div className="sidebar-right-content-container">
|
||||
<MachineMechanics />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{activeList === "analysis" && (
|
||||
{subModule === "mechanics" && !selectedActionSphere && (
|
||||
<div className="sidebar-right-container">
|
||||
<div className="sidebar-right-content-container">
|
||||
<MachineMechanics />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{subModule === "analysis" && (
|
||||
<div className="sidebar-right-container">
|
||||
<div className="sidebar-right-content-container">
|
||||
<Analysis />
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
import React from "react";
|
||||
import { EyeDroperIcon } from "../../../icons/ExportCommonIcons";
|
||||
|
||||
interface PositionInputProps {
|
||||
onChange: (value: string) => void; // Callback for value change
|
||||
header: string;
|
||||
placeholder?: string; // Optional placeholder
|
||||
type?: string; // Input type (e.g., text, number, email)
|
||||
}
|
||||
|
||||
const Vector3Input: React.FC<PositionInputProps> = ({
|
||||
onChange,
|
||||
header,
|
||||
placeholder = "Enter value", // Default placeholder
|
||||
type = "number", // Default type
|
||||
}) => {
|
||||
return (
|
||||
<div className="custom-input-container">
|
||||
<div className="header">
|
||||
{header}{" "}
|
||||
<div className="eyedrop-button">
|
||||
<EyeDroperIcon isActive={false} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="inputs-container">
|
||||
<div className="input-container">
|
||||
<div className="custom-input-label">X : </div>
|
||||
<input
|
||||
className="custom-input-field"
|
||||
type={type}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
placeholder={placeholder}
|
||||
disabled
|
||||
/>
|
||||
</div>
|
||||
<div className="input-container">
|
||||
<div className="custom-input-label">Y : </div>
|
||||
<input
|
||||
className="custom-input-field"
|
||||
type={type}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
placeholder={placeholder}
|
||||
disabled
|
||||
min={0}
|
||||
/>
|
||||
</div>
|
||||
<div className="input-container">
|
||||
<div className="custom-input-label">Z : </div>
|
||||
<input
|
||||
className="custom-input-field"
|
||||
type={type}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
placeholder={placeholder}
|
||||
disabled
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Vector3Input;
|
||||
@@ -11,8 +11,11 @@ import LabledDropdown from "../../../ui/inputs/LabledDropdown";
|
||||
import RegularDropDown from "../../../ui/inputs/RegularDropDown";
|
||||
import { handleResize } from "../../../../functions/handleResizePannel";
|
||||
import EyeDropInput from "../../../ui/inputs/EyeDropInput";
|
||||
import { useSelectedActionSphere } from "../../../../store/store";
|
||||
|
||||
const MachineMechanics: React.FC = () => {
|
||||
const { selectedActionSphere } = useSelectedActionSphere();
|
||||
console.log("selectedActionSphere: ", selectedActionSphere);
|
||||
const [actionList, setActionList] = useState<string[]>([]);
|
||||
const [triggerList, setTriggerList] = useState<string[]>([]);
|
||||
const [selectedItem, setSelectedItem] = useState<{
|
||||
@@ -68,8 +71,10 @@ const MachineMechanics: React.FC = () => {
|
||||
|
||||
return (
|
||||
<div className="machine-mechanics-container">
|
||||
<div className="machine-mechanics-header">Selected Object</div>
|
||||
<div className="process-list-container">
|
||||
<div className="machine-mechanics-header">
|
||||
{selectedActionSphere?.path?.modelName || "path name not found"}
|
||||
</div>
|
||||
{/* <div className="process-list-container">
|
||||
<div className="label">Process:</div>
|
||||
<RegularDropDown
|
||||
header={activeProcess || "add process ->"}
|
||||
@@ -79,7 +84,7 @@ const MachineMechanics: React.FC = () => {
|
||||
<div className="add-new-process" onClick={handleAddProcess}>
|
||||
<AddIcon />
|
||||
</div>
|
||||
</div>
|
||||
</div> */}
|
||||
<div className="machine-mechanics-content-container">
|
||||
<div className="actions">
|
||||
<div className="header">
|
||||
|
||||
@@ -44,12 +44,13 @@ const AssetProperties: React.FC = () => {
|
||||
|
||||
return (
|
||||
<div className="asset-properties-container">
|
||||
{/* Name */}
|
||||
<div className="header">Selected Object</div>
|
||||
|
||||
<div className="split"></div>
|
||||
|
||||
<PositionInput onChange={() => {}} />
|
||||
<RotationInput onChange={() => {}} />
|
||||
<PositionInput onChange={() => { }} />
|
||||
<RotationInput onChange={() => { }} />
|
||||
|
||||
<div className="split"></div>
|
||||
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
import React, { useState } from "react";
|
||||
import RenameInput from "../../../ui/inputs/RenameInput";
|
||||
import Vector3Input from "../customInput/Vector3Input";
|
||||
|
||||
const ZoneProperties: React.FC = () => {
|
||||
const [Edit, setEdit] = useState(false);
|
||||
|
||||
function handleSetView() {
|
||||
setEdit(false);
|
||||
}
|
||||
|
||||
function handleEditView() {
|
||||
if (Edit) {
|
||||
setEdit(false);
|
||||
} else {
|
||||
setEdit(true);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="zone-properties-container">
|
||||
<div className="header">
|
||||
<RenameInput value="Selected Zone Name" />
|
||||
<div className="button" onClick={handleEditView}>
|
||||
{Edit ? "Cancel" : "Edit"}
|
||||
</div>
|
||||
</div>
|
||||
<Vector3Input onChange={() => {}} header="Viewport Target" />
|
||||
<Vector3Input onChange={() => {}} header="Viewport Position" />
|
||||
{Edit && (
|
||||
<div className="button-save" onClick={handleSetView}>
|
||||
Set View
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ZoneProperties;
|
||||
@@ -28,7 +28,7 @@ const DropList: React.FC<DropListProps> = ({ val }) => {
|
||||
}}
|
||||
>
|
||||
{val.pathName}
|
||||
<div className="arrow-container">
|
||||
<div className={`arrow-container${openDrop ? " active" : ""}`}>
|
||||
<ArrowIcon />
|
||||
</div>
|
||||
</div>
|
||||
@@ -123,8 +123,8 @@ const Simulations: React.FC = () => {
|
||||
<ArrowIcon />
|
||||
</div>
|
||||
</div>
|
||||
{Value.map((val) => (
|
||||
<DropList val={val} />
|
||||
{Value.map((val, index) => (
|
||||
<DropList key={index} val={val} />
|
||||
))}
|
||||
</div>
|
||||
<div className="compare-simulations-container">
|
||||
|
||||
@@ -6,15 +6,20 @@ import {
|
||||
SimulationIcon,
|
||||
VisualizationIcon,
|
||||
} from "../icons/ExportModuleIcons";
|
||||
import useToggleStore from "../../store/useUIToggleStore";
|
||||
|
||||
const ModuleToggle: React.FC = () => {
|
||||
const { activeModule, setActiveModule } = useModuleStore();
|
||||
const { setToggleUI } = useToggleStore();
|
||||
|
||||
return (
|
||||
<div className="module-toggle-container">
|
||||
<div
|
||||
className={`module-list ${activeModule === "builder" && "active"}`}
|
||||
onClick={() => setActiveModule("builder")}
|
||||
onClick={() => {
|
||||
setActiveModule("builder");
|
||||
setToggleUI(true);
|
||||
}}
|
||||
>
|
||||
<div className="icon">
|
||||
<BuilderIcon isActive={activeModule === "builder"} />
|
||||
@@ -23,7 +28,10 @@ const ModuleToggle: React.FC = () => {
|
||||
</div>
|
||||
<div
|
||||
className={`module-list ${activeModule === "simulation" && "active"}`}
|
||||
onClick={() => setActiveModule("simulation")}
|
||||
onClick={() => {
|
||||
setActiveModule("simulation");
|
||||
setToggleUI(true);
|
||||
}}
|
||||
>
|
||||
<div className="icon">
|
||||
<SimulationIcon isActive={activeModule === "simulation"} />
|
||||
@@ -34,7 +42,10 @@ const ModuleToggle: React.FC = () => {
|
||||
className={`module-list ${
|
||||
activeModule === "visualization" && "active"
|
||||
}`}
|
||||
onClick={() => setActiveModule("visualization")}
|
||||
onClick={() => {
|
||||
setActiveModule("visualization");
|
||||
setToggleUI(true);
|
||||
}}
|
||||
>
|
||||
<div className="icon">
|
||||
<VisualizationIcon isActive={activeModule === "visualization"} />
|
||||
@@ -42,10 +53,11 @@ const ModuleToggle: React.FC = () => {
|
||||
<div className="module">Visualization</div>
|
||||
</div>
|
||||
<div
|
||||
className={`module-list ${
|
||||
activeModule === "market" && "active"
|
||||
}`}
|
||||
onClick={() => setActiveModule("market")}
|
||||
className={`module-list ${activeModule === "market" && "active"}`}
|
||||
onClick={() => {
|
||||
setActiveModule("market");
|
||||
setToggleUI(false);
|
||||
}}
|
||||
>
|
||||
<div className="icon">
|
||||
<CartIcon isActive={activeModule === "market"} />
|
||||
|
||||
@@ -17,6 +17,12 @@ import { handleSaveTemplate } from "../../modules/visualization/handleSaveTempla
|
||||
import { usePlayButtonStore } from "../../store/usePlayButtonStore";
|
||||
import useTemplateStore from "../../store/useTemplateStore";
|
||||
import { useSelectedZoneStore } from "../../store/useZoneStore";
|
||||
import {
|
||||
useAddAction,
|
||||
useDeleteModels,
|
||||
useSelectedWallItem,
|
||||
useToggleView,
|
||||
} from "../../store/store";
|
||||
|
||||
const Tools: React.FC = () => {
|
||||
const { templates } = useTemplateStore();
|
||||
@@ -32,12 +38,30 @@ const Tools: React.FC = () => {
|
||||
const { addTemplate } = useTemplateStore();
|
||||
const { selectedZone } = useSelectedZoneStore();
|
||||
|
||||
// wall options
|
||||
const { setToggleView } = useToggleView();
|
||||
const { setDeleteModels } = useDeleteModels();
|
||||
const { setAddAction } = useAddAction();
|
||||
const { setSelectedWallItem } = useSelectedWallItem();
|
||||
|
||||
// Reset activeTool whenever activeModule changes
|
||||
useEffect(() => {
|
||||
setActiveTool(activeSubTool);
|
||||
setActiveSubTool(activeSubTool);
|
||||
}, [activeModule]);
|
||||
|
||||
const toggleSwitch = () => {
|
||||
if (toggleThreeD) {
|
||||
setSelectedWallItem(null);
|
||||
setDeleteModels(false);
|
||||
setAddAction(null);
|
||||
setToggleView(true);
|
||||
} else {
|
||||
setToggleView(false);
|
||||
}
|
||||
setToggleThreeD(!toggleThreeD);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const handleOutsideClick = (event: MouseEvent) => {
|
||||
if (
|
||||
@@ -47,217 +71,225 @@ 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
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="tools-container">
|
||||
{!isPlaying && (
|
||||
<div className="drop-down-icons">
|
||||
<div className="activeDropicon">
|
||||
{activeSubTool == "cursor" && (
|
||||
<div
|
||||
className={`tool-button ${
|
||||
activeTool === "cursor" ? "active" : ""
|
||||
}`}
|
||||
onClick={() => {
|
||||
setActiveTool("cursor");
|
||||
}}
|
||||
>
|
||||
<CursorIcon isActive={activeTool === "cursor"} />
|
||||
</div>
|
||||
)}
|
||||
{activeSubTool == "free-hand" && (
|
||||
<div
|
||||
className={`tool-button ${
|
||||
activeTool === "free-hand" ? "active" : ""
|
||||
}`}
|
||||
onClick={() => {
|
||||
setActiveTool("free-hand");
|
||||
}}
|
||||
>
|
||||
<FreeMoveIcon isActive={activeTool === "free-hand"} />
|
||||
</div>
|
||||
)}
|
||||
{activeModule !== "visualization" && (
|
||||
<div
|
||||
className="drop-down-option-button"
|
||||
ref={dropdownRef}
|
||||
onClick={() => {
|
||||
setOpenDrop(!openDrop);
|
||||
console.log(openDrop);
|
||||
}}
|
||||
>
|
||||
<ArrowIcon />
|
||||
{openDrop && (
|
||||
<div className="drop-down-container">
|
||||
<div
|
||||
className="option-list"
|
||||
onClick={() => {
|
||||
setOpenDrop(false);
|
||||
setActiveTool("cursor");
|
||||
setActiveSubTool("cursor");
|
||||
}}
|
||||
>
|
||||
<div className="active-option">
|
||||
{activeSubTool === "cursor" && <TickIcon />}
|
||||
<>
|
||||
{!isPlaying ? (
|
||||
<>
|
||||
<div className="tools-container">
|
||||
<div className="drop-down-icons">
|
||||
<div className="activeDropicon">
|
||||
{activeSubTool == "cursor" && (
|
||||
<div
|
||||
className={`tool-button ${
|
||||
activeTool === "cursor" ? "active" : ""
|
||||
}`}
|
||||
onClick={() => {
|
||||
setActiveTool("cursor");
|
||||
}}
|
||||
>
|
||||
<CursorIcon isActive={activeTool === "cursor"} />
|
||||
</div>
|
||||
)}
|
||||
{activeSubTool == "free-hand" && (
|
||||
<div
|
||||
className={`tool-button ${
|
||||
activeTool === "free-hand" ? "active" : ""
|
||||
}`}
|
||||
onClick={() => {
|
||||
setActiveTool("free-hand");
|
||||
}}
|
||||
>
|
||||
<FreeMoveIcon isActive={activeTool === "free-hand"} />
|
||||
</div>
|
||||
)}
|
||||
{activeModule !== "visualization" && (
|
||||
<div
|
||||
className="drop-down-option-button"
|
||||
ref={dropdownRef}
|
||||
onClick={() => {
|
||||
setOpenDrop(!openDrop);
|
||||
console.log(openDrop);
|
||||
}}
|
||||
>
|
||||
<ArrowIcon />
|
||||
{openDrop && (
|
||||
<div className="drop-down-container">
|
||||
<div
|
||||
className="option-list"
|
||||
onClick={() => {
|
||||
setOpenDrop(false);
|
||||
setActiveTool("cursor");
|
||||
setActiveSubTool("cursor");
|
||||
}}
|
||||
>
|
||||
<div className="active-option">
|
||||
{activeSubTool === "cursor" && <TickIcon />}
|
||||
</div>
|
||||
<CursorIcon isActive={false} />
|
||||
<div className="option">Cursor</div>
|
||||
</div>
|
||||
<div
|
||||
className="option-list"
|
||||
onClick={() => {
|
||||
setOpenDrop(false);
|
||||
setActiveTool("free-hand");
|
||||
setActiveSubTool("free-hand");
|
||||
}}
|
||||
>
|
||||
<div className="active-option">
|
||||
{activeSubTool === "free-hand" && <TickIcon />}
|
||||
</div>
|
||||
<FreeMoveIcon isActive={false} />
|
||||
<div className="option">Free Hand</div>
|
||||
</div>
|
||||
</div>
|
||||
<CursorIcon isActive={false} />
|
||||
<div className="option">Cursor</div>
|
||||
</div>
|
||||
<div
|
||||
className="option-list"
|
||||
onClick={() => {
|
||||
setOpenDrop(false);
|
||||
setActiveTool("free-hand");
|
||||
setActiveSubTool("free-hand");
|
||||
}}
|
||||
>
|
||||
<div className="active-option">
|
||||
{activeSubTool === "free-hand" && <TickIcon />}
|
||||
</div>
|
||||
<FreeMoveIcon isActive={false} />
|
||||
<div className="option">Free Hand</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{!toggleThreeD && activeModule === "builder" && (
|
||||
<>
|
||||
<div className="split"></div>
|
||||
<div className="draw-tools">
|
||||
<div
|
||||
className={`tool-button ${
|
||||
activeTool === "draw-wall" ? "active" : ""
|
||||
}`}
|
||||
onClick={() => {
|
||||
setActiveTool("draw-wall");
|
||||
}}
|
||||
>
|
||||
<WallIcon isActive={activeTool === "draw-wall"} />
|
||||
</div>
|
||||
<div
|
||||
className={`tool-button ${
|
||||
activeTool === "draw-zone" ? "active" : ""
|
||||
}`}
|
||||
onClick={() => {
|
||||
setActiveTool("draw-zone");
|
||||
}}
|
||||
>
|
||||
<ZoneIcon isActive={activeTool === "draw-zone"} />
|
||||
</div>
|
||||
<div
|
||||
className={`tool-button ${
|
||||
activeTool === "draw-aisle" ? "active" : ""
|
||||
}`}
|
||||
onClick={() => {
|
||||
setActiveTool("draw-aisle");
|
||||
}}
|
||||
>
|
||||
<AsileIcon isActive={activeTool === "draw-aisle"} />
|
||||
</div>
|
||||
<div
|
||||
className={`tool-button ${
|
||||
activeTool === "draw-floor" ? "active" : ""
|
||||
}`}
|
||||
onClick={() => {
|
||||
setActiveTool("draw-floor");
|
||||
}}
|
||||
>
|
||||
<FloorIcon isActive={activeTool === "draw-floor"} />
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{!toggleThreeD && activeModule === "builder" && (
|
||||
<>
|
||||
<div className="split"></div>
|
||||
<div className="draw-tools">
|
||||
<div
|
||||
className={`tool-button ${
|
||||
activeTool === "draw-wall" ? "active" : ""
|
||||
}`}
|
||||
onClick={() => {
|
||||
setActiveTool("draw-wall");
|
||||
}}
|
||||
>
|
||||
<WallIcon isActive={activeTool === "draw-wall"} />
|
||||
{activeModule === "simulation" && (
|
||||
<>
|
||||
<div className="split"></div>
|
||||
<div className="draw-tools">
|
||||
<div
|
||||
className={`tool-button ${
|
||||
activeTool === "pen" ? "active" : ""
|
||||
}`}
|
||||
onClick={() => {
|
||||
setActiveTool("pen");
|
||||
}}
|
||||
>
|
||||
<PenIcon isActive={activeTool === "pen"} />
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{activeModule === "visualization" && (
|
||||
<>
|
||||
<div className="split"></div>
|
||||
<div className="draw-tools">
|
||||
<div
|
||||
className={`tool-button`}
|
||||
onClick={() =>
|
||||
handleSaveTemplate({
|
||||
addTemplate,
|
||||
selectedZone,
|
||||
templates,
|
||||
})
|
||||
}
|
||||
>
|
||||
<SaveTemplateIcon isActive={false} />
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<div className="split"></div>
|
||||
<div className="general-options">
|
||||
<div
|
||||
className={`tool-button ${
|
||||
activeTool === "comment" ? "active" : ""
|
||||
}`}
|
||||
onClick={() => {
|
||||
setActiveTool("comment");
|
||||
}}
|
||||
>
|
||||
<CommentIcon isActive={activeTool === "comment"} />
|
||||
</div>
|
||||
<div
|
||||
className={`tool-button ${
|
||||
activeTool === "play" ? "active" : ""
|
||||
}`}
|
||||
onClick={() => {
|
||||
setIsPlaying(!isPlaying);
|
||||
}}
|
||||
>
|
||||
<PlayIcon isActive={activeTool === "play"} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="split"></div>
|
||||
<div
|
||||
className={`tool-button ${
|
||||
activeTool === "draw-zone" ? "active" : ""
|
||||
className={`toggle-threed-button${
|
||||
toggleThreeD ? " toggled" : ""
|
||||
}`}
|
||||
onClick={() => {
|
||||
setActiveTool("draw-zone");
|
||||
}}
|
||||
onClick={toggleSwitch}
|
||||
>
|
||||
<ZoneIcon isActive={activeTool === "draw-zone"} />
|
||||
</div>
|
||||
<div
|
||||
className={`tool-button ${
|
||||
activeTool === "draw-aisle" ? "active" : ""
|
||||
}`}
|
||||
onClick={() => {
|
||||
setActiveTool("draw-aisle");
|
||||
}}
|
||||
>
|
||||
<AsileIcon isActive={activeTool === "draw-aisle"} />
|
||||
</div>
|
||||
<div
|
||||
className={`tool-button ${
|
||||
activeTool === "draw-floor" ? "active" : ""
|
||||
}`}
|
||||
onClick={() => {
|
||||
setActiveTool("draw-floor");
|
||||
}}
|
||||
>
|
||||
<FloorIcon isActive={activeTool === "draw-floor"} />
|
||||
<div className={`toggle-option${!toggleThreeD ? " active" : ""}`}>
|
||||
2d
|
||||
</div>
|
||||
<div className={`toggle-option${toggleThreeD ? " active" : ""}`}>
|
||||
3d
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{activeModule === "simulation" && (
|
||||
<>
|
||||
<div className="split"></div>
|
||||
<div className="draw-tools">
|
||||
<div
|
||||
className={`tool-button ${activeTool === "pen" ? "active" : ""}`}
|
||||
onClick={() => {
|
||||
setActiveTool("pen");
|
||||
}}
|
||||
>
|
||||
<PenIcon isActive={activeTool === "pen"} />
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{activeModule === "visualization" && !isPlaying && (
|
||||
<>
|
||||
<div className="split"></div>
|
||||
<div className="draw-tools">
|
||||
<div
|
||||
className={`tool-button`}
|
||||
onClick={() =>
|
||||
handleSaveTemplate({
|
||||
addTemplate,
|
||||
selectedZone,
|
||||
templates,
|
||||
})
|
||||
}
|
||||
>
|
||||
<SaveTemplateIcon isActive={false} />
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{!isPlaying && (
|
||||
<>
|
||||
<div className="split"></div>
|
||||
<div className="general-options">
|
||||
<div
|
||||
className={`tool-button ${
|
||||
activeTool === "comment" ? "active" : ""
|
||||
}`}
|
||||
onClick={() => {
|
||||
setActiveTool("comment");
|
||||
}}
|
||||
>
|
||||
<CommentIcon isActive={activeTool === "comment"} />
|
||||
</div>
|
||||
<div
|
||||
className={`tool-button ${activeTool === "play" ? "active" : ""}`}
|
||||
onClick={() => {
|
||||
setActiveTool("play");
|
||||
setIsPlaying(!isPlaying);
|
||||
}}
|
||||
>
|
||||
<PlayIcon isActive={activeTool === "play"} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="split"></div>
|
||||
<div
|
||||
className={`toggle-threed-button${toggleThreeD ? " toggled" : ""}`}
|
||||
onClick={() => {
|
||||
setToggleThreeD(!toggleThreeD);
|
||||
}}
|
||||
>
|
||||
<div className={`toggle-option${!toggleThreeD ? " active" : ""}`}>
|
||||
2d
|
||||
</div>
|
||||
<div className={`toggle-option${toggleThreeD ? " active" : ""}`}>
|
||||
3d
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{isPlaying && (
|
||||
) : (
|
||||
<div className="exitPlay" onClick={() => setIsPlaying(false)}>
|
||||
X
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,94 +1,94 @@
|
||||
import { useMemo } from "react";
|
||||
|
||||
import { Bar } from "react-chartjs-2";
|
||||
|
||||
interface ChartComponentProps {
|
||||
type: any;
|
||||
title: string;
|
||||
fontFamily?: string;
|
||||
fontSize?: string;
|
||||
fontWeight?: "Light" | "Regular" | "Bold";
|
||||
data: any;
|
||||
}
|
||||
|
||||
const LineGraphComponent = ({
|
||||
title,
|
||||
fontFamily,
|
||||
fontSize,
|
||||
fontWeight = "Regular",
|
||||
}: ChartComponentProps) => {
|
||||
// Memoize Font Weight Mapping
|
||||
const chartFontWeightMap = useMemo(
|
||||
() => ({
|
||||
Light: "lighter" as const,
|
||||
Regular: "normal" as const,
|
||||
Bold: "bold" as const,
|
||||
}),
|
||||
[]
|
||||
);
|
||||
|
||||
// Parse and Memoize Font Size
|
||||
const fontSizeValue = useMemo(
|
||||
() => (fontSize ? parseInt(fontSize) : 12),
|
||||
[fontSize]
|
||||
);
|
||||
|
||||
// Determine and Memoize Font Weight
|
||||
const fontWeightValue = useMemo(
|
||||
() => chartFontWeightMap[fontWeight],
|
||||
[fontWeight, chartFontWeightMap]
|
||||
);
|
||||
|
||||
// Memoize Chart Font Style
|
||||
const chartFontStyle = useMemo(
|
||||
() => ({
|
||||
family: fontFamily || "Arial",
|
||||
size: fontSizeValue,
|
||||
weight: fontWeightValue,
|
||||
}),
|
||||
[fontFamily, fontSizeValue, fontWeightValue]
|
||||
);
|
||||
|
||||
const options = useMemo(
|
||||
() => ({
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
title: {
|
||||
display: true,
|
||||
text: title,
|
||||
font: chartFontStyle,
|
||||
},
|
||||
legend: {
|
||||
display: false,
|
||||
},
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
ticks: {
|
||||
display: false, // This hides the x-axis labels
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
[title, chartFontStyle]
|
||||
);
|
||||
|
||||
const chartData = {
|
||||
labels: ["January", "February", "March", "April", "May", "June", "July"],
|
||||
datasets: [
|
||||
{
|
||||
label: "My First Dataset",
|
||||
data: [65, 59, 80, 81, 56, 55, 40],
|
||||
backgroundColor: "#6f42c1",
|
||||
borderColor: "#ffffff",
|
||||
borderWidth: 2,
|
||||
fill: false,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
return <Bar data={chartData} options={options} />;
|
||||
};
|
||||
|
||||
export default LineGraphComponent;
|
||||
import { useMemo } from "react";
|
||||
|
||||
import { Bar } from "react-chartjs-2";
|
||||
|
||||
interface ChartComponentProps {
|
||||
type: any;
|
||||
title: string;
|
||||
fontFamily?: string;
|
||||
fontSize?: string;
|
||||
fontWeight?: "Light" | "Regular" | "Bold";
|
||||
data: any;
|
||||
}
|
||||
|
||||
const LineGraphComponent = ({
|
||||
title,
|
||||
fontFamily,
|
||||
fontSize,
|
||||
fontWeight = "Regular",
|
||||
}: ChartComponentProps) => {
|
||||
// Memoize Font Weight Mapping
|
||||
const chartFontWeightMap = useMemo(
|
||||
() => ({
|
||||
Light: "lighter" as const,
|
||||
Regular: "normal" as const,
|
||||
Bold: "bold" as const,
|
||||
}),
|
||||
[]
|
||||
);
|
||||
|
||||
// Parse and Memoize Font Size
|
||||
const fontSizeValue = useMemo(
|
||||
() => (fontSize ? parseInt(fontSize) : 12),
|
||||
[fontSize]
|
||||
);
|
||||
|
||||
// Determine and Memoize Font Weight
|
||||
const fontWeightValue = useMemo(
|
||||
() => chartFontWeightMap[fontWeight],
|
||||
[fontWeight, chartFontWeightMap]
|
||||
);
|
||||
|
||||
// Memoize Chart Font Style
|
||||
const chartFontStyle = useMemo(
|
||||
() => ({
|
||||
family: fontFamily || "Arial",
|
||||
size: fontSizeValue,
|
||||
weight: fontWeightValue,
|
||||
}),
|
||||
[fontFamily, fontSizeValue, fontWeightValue]
|
||||
);
|
||||
|
||||
const options = useMemo(
|
||||
() => ({
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
title: {
|
||||
display: true,
|
||||
text: title,
|
||||
font: chartFontStyle,
|
||||
},
|
||||
legend: {
|
||||
display: false,
|
||||
},
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
ticks: {
|
||||
display: false, // This hides the x-axis labels
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
[title, chartFontStyle]
|
||||
);
|
||||
|
||||
const chartData = {
|
||||
labels: ["January", "February", "March", "April", "May", "June", "July"],
|
||||
datasets: [
|
||||
{
|
||||
label: "My First Dataset",
|
||||
data: [65, 59, 80, 81, 56, 55, 40],
|
||||
backgroundColor: "#6f42c1",
|
||||
borderColor: "#ffffff",
|
||||
borderWidth: 2,
|
||||
fill: false,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
return <Bar data={chartData} options={options} />;
|
||||
};
|
||||
|
||||
export default LineGraphComponent;
|
||||
|
||||
@@ -1,93 +1,93 @@
|
||||
import { useMemo } from "react";
|
||||
import { Line } from "react-chartjs-2";
|
||||
|
||||
interface ChartComponentProps {
|
||||
type: any;
|
||||
title: string;
|
||||
fontFamily?: string;
|
||||
fontSize?: string;
|
||||
fontWeight?: "Light" | "Regular" | "Bold";
|
||||
data: any;
|
||||
}
|
||||
|
||||
const LineGraphComponent = ({
|
||||
title,
|
||||
fontFamily,
|
||||
fontSize,
|
||||
fontWeight = "Regular",
|
||||
}: ChartComponentProps) => {
|
||||
// Memoize Font Weight Mapping
|
||||
const chartFontWeightMap = useMemo(
|
||||
() => ({
|
||||
Light: "lighter" as const,
|
||||
Regular: "normal" as const,
|
||||
Bold: "bold" as const,
|
||||
}),
|
||||
[]
|
||||
);
|
||||
|
||||
// Parse and Memoize Font Size
|
||||
const fontSizeValue = useMemo(
|
||||
() => (fontSize ? parseInt(fontSize) : 12),
|
||||
[fontSize]
|
||||
);
|
||||
|
||||
// Determine and Memoize Font Weight
|
||||
const fontWeightValue = useMemo(
|
||||
() => chartFontWeightMap[fontWeight],
|
||||
[fontWeight, chartFontWeightMap]
|
||||
);
|
||||
|
||||
// Memoize Chart Font Style
|
||||
const chartFontStyle = useMemo(
|
||||
() => ({
|
||||
family: fontFamily || "Arial",
|
||||
size: fontSizeValue,
|
||||
weight: fontWeightValue,
|
||||
}),
|
||||
[fontFamily, fontSizeValue, fontWeightValue]
|
||||
);
|
||||
|
||||
const options = useMemo(
|
||||
() => ({
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
title: {
|
||||
display: true,
|
||||
text: title,
|
||||
font: chartFontStyle,
|
||||
},
|
||||
legend: {
|
||||
display: false,
|
||||
},
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
ticks: {
|
||||
display: false, // This hides the x-axis labels
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
[title, chartFontStyle]
|
||||
);
|
||||
|
||||
const chartData = {
|
||||
labels: ["January", "February", "March", "April", "May", "June", "July"],
|
||||
datasets: [
|
||||
{
|
||||
label: "My First Dataset",
|
||||
data: [65, 59, 80, 81, 56, 55, 40],
|
||||
backgroundColor: "#6f42c1", // Updated to #6f42c1 (Purple)
|
||||
borderColor: "#ffffff", // Keeping border color white
|
||||
borderWidth: 2,
|
||||
fill: false,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
return <Line data={chartData} options={options} />;
|
||||
};
|
||||
|
||||
export default LineGraphComponent;
|
||||
import { useMemo } from "react";
|
||||
import { Line } from "react-chartjs-2";
|
||||
|
||||
interface ChartComponentProps {
|
||||
type: any;
|
||||
title: string;
|
||||
fontFamily?: string;
|
||||
fontSize?: string;
|
||||
fontWeight?: "Light" | "Regular" | "Bold";
|
||||
data: any;
|
||||
}
|
||||
|
||||
const LineGraphComponent = ({
|
||||
title,
|
||||
fontFamily,
|
||||
fontSize,
|
||||
fontWeight = "Regular",
|
||||
}: ChartComponentProps) => {
|
||||
// Memoize Font Weight Mapping
|
||||
const chartFontWeightMap = useMemo(
|
||||
() => ({
|
||||
Light: "lighter" as const,
|
||||
Regular: "normal" as const,
|
||||
Bold: "bold" as const,
|
||||
}),
|
||||
[]
|
||||
);
|
||||
|
||||
// Parse and Memoize Font Size
|
||||
const fontSizeValue = useMemo(
|
||||
() => (fontSize ? parseInt(fontSize) : 12),
|
||||
[fontSize]
|
||||
);
|
||||
|
||||
// Determine and Memoize Font Weight
|
||||
const fontWeightValue = useMemo(
|
||||
() => chartFontWeightMap[fontWeight],
|
||||
[fontWeight, chartFontWeightMap]
|
||||
);
|
||||
|
||||
// Memoize Chart Font Style
|
||||
const chartFontStyle = useMemo(
|
||||
() => ({
|
||||
family: fontFamily || "Arial",
|
||||
size: fontSizeValue,
|
||||
weight: fontWeightValue,
|
||||
}),
|
||||
[fontFamily, fontSizeValue, fontWeightValue]
|
||||
);
|
||||
|
||||
const options = useMemo(
|
||||
() => ({
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
title: {
|
||||
display: true,
|
||||
text: title,
|
||||
font: chartFontStyle,
|
||||
},
|
||||
legend: {
|
||||
display: false,
|
||||
},
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
ticks: {
|
||||
display: false, // This hides the x-axis labels
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
[title, chartFontStyle]
|
||||
);
|
||||
|
||||
const chartData = {
|
||||
labels: ["January", "February", "March", "April", "May", "June", "July"],
|
||||
datasets: [
|
||||
{
|
||||
label: "My First Dataset",
|
||||
data: [65, 59, 80, 81, 56, 55, 40],
|
||||
backgroundColor: "#6f42c1", // Updated to #6f42c1 (Purple)
|
||||
borderColor: "#ffffff", // Keeping border color white
|
||||
borderWidth: 2,
|
||||
fill: false,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
return <Line data={chartData} options={options} />;
|
||||
};
|
||||
|
||||
export default LineGraphComponent;
|
||||
|
||||
@@ -1,91 +1,91 @@
|
||||
import { useMemo } from "react";
|
||||
import { Pie } from "react-chartjs-2";
|
||||
|
||||
interface ChartComponentProps {
|
||||
type: any;
|
||||
title: string;
|
||||
fontFamily?: string;
|
||||
fontSize?: string;
|
||||
fontWeight?: "Light" | "Regular" | "Bold";
|
||||
data: any;
|
||||
}
|
||||
|
||||
const PieChartComponent = ({
|
||||
title,
|
||||
fontFamily,
|
||||
fontSize,
|
||||
fontWeight = "Regular",
|
||||
}: ChartComponentProps) => {
|
||||
// Memoize Font Weight Mapping
|
||||
const chartFontWeightMap = useMemo(
|
||||
() => ({
|
||||
Light: "lighter" as const,
|
||||
Regular: "normal" as const,
|
||||
Bold: "bold" as const,
|
||||
}),
|
||||
[]
|
||||
);
|
||||
|
||||
// Parse and Memoize Font Size
|
||||
const fontSizeValue = useMemo(
|
||||
() => (fontSize ? parseInt(fontSize) : 12),
|
||||
[fontSize]
|
||||
);
|
||||
|
||||
// Determine and Memoize Font Weight
|
||||
const fontWeightValue = useMemo(
|
||||
() => chartFontWeightMap[fontWeight],
|
||||
[fontWeight, chartFontWeightMap]
|
||||
);
|
||||
|
||||
// Memoize Chart Font Style
|
||||
const chartFontStyle = useMemo(
|
||||
() => ({
|
||||
family: fontFamily || "Arial",
|
||||
size: fontSizeValue,
|
||||
weight: fontWeightValue,
|
||||
}),
|
||||
[fontFamily, fontSizeValue, fontWeightValue]
|
||||
);
|
||||
|
||||
// Access the CSS variable for the primary accent color
|
||||
const accentColor = getComputedStyle(document.documentElement)
|
||||
.getPropertyValue("--accent-color")
|
||||
.trim();
|
||||
|
||||
console.log("accentColor: ", accentColor);
|
||||
const options = useMemo(
|
||||
() => ({
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
title: {
|
||||
display: true,
|
||||
text: title,
|
||||
font: chartFontStyle,
|
||||
},
|
||||
legend: {
|
||||
display: false,
|
||||
},
|
||||
},
|
||||
}),
|
||||
[title, chartFontStyle]
|
||||
);
|
||||
|
||||
const chartData = {
|
||||
labels: ["Red", "Blue", "Yellow", "Green", "Purple", "Orange"],
|
||||
datasets: [
|
||||
{
|
||||
label: "Dataset",
|
||||
data: [12, 19, 3, 5, 2, 3],
|
||||
backgroundColor: ["#6f42c1"],
|
||||
borderColor: "#ffffff",
|
||||
borderWidth: 2,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
return <Pie data={chartData} options={options} />;
|
||||
};
|
||||
|
||||
export default PieChartComponent;
|
||||
import { useMemo } from "react";
|
||||
import { Pie } from "react-chartjs-2";
|
||||
|
||||
interface ChartComponentProps {
|
||||
type: any;
|
||||
title: string;
|
||||
fontFamily?: string;
|
||||
fontSize?: string;
|
||||
fontWeight?: "Light" | "Regular" | "Bold";
|
||||
data: any;
|
||||
}
|
||||
|
||||
const PieChartComponent = ({
|
||||
title,
|
||||
fontFamily,
|
||||
fontSize,
|
||||
fontWeight = "Regular",
|
||||
}: ChartComponentProps) => {
|
||||
// Memoize Font Weight Mapping
|
||||
const chartFontWeightMap = useMemo(
|
||||
() => ({
|
||||
Light: "lighter" as const,
|
||||
Regular: "normal" as const,
|
||||
Bold: "bold" as const,
|
||||
}),
|
||||
[]
|
||||
);
|
||||
|
||||
// Parse and Memoize Font Size
|
||||
const fontSizeValue = useMemo(
|
||||
() => (fontSize ? parseInt(fontSize) : 12),
|
||||
[fontSize]
|
||||
);
|
||||
|
||||
// Determine and Memoize Font Weight
|
||||
const fontWeightValue = useMemo(
|
||||
() => chartFontWeightMap[fontWeight],
|
||||
[fontWeight, chartFontWeightMap]
|
||||
);
|
||||
|
||||
// Memoize Chart Font Style
|
||||
const chartFontStyle = useMemo(
|
||||
() => ({
|
||||
family: fontFamily || "Arial",
|
||||
size: fontSizeValue,
|
||||
weight: fontWeightValue,
|
||||
}),
|
||||
[fontFamily, fontSizeValue, fontWeightValue]
|
||||
);
|
||||
|
||||
// Access the CSS variable for the primary accent color
|
||||
const accentColor = getComputedStyle(document.documentElement)
|
||||
.getPropertyValue("--accent-color")
|
||||
.trim();
|
||||
|
||||
console.log("accentColor: ", accentColor);
|
||||
const options = useMemo(
|
||||
() => ({
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
title: {
|
||||
display: true,
|
||||
text: title,
|
||||
font: chartFontStyle,
|
||||
},
|
||||
legend: {
|
||||
display: false,
|
||||
},
|
||||
},
|
||||
}),
|
||||
[title, chartFontStyle]
|
||||
);
|
||||
|
||||
const chartData = {
|
||||
labels: ["Red", "Blue", "Yellow", "Green", "Purple", "Orange"],
|
||||
datasets: [
|
||||
{
|
||||
label: "Dataset",
|
||||
data: [12, 19, 3, 5, 2, 3],
|
||||
backgroundColor: ["#6f42c1"],
|
||||
borderColor: "#ffffff",
|
||||
borderWidth: 2,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
return <Pie data={chartData} options={options} />;
|
||||
};
|
||||
|
||||
export default PieChartComponent;
|
||||
|
||||
@@ -109,6 +109,7 @@ const AddButtons: React.FC<ButtonsProps> = ({
|
||||
};
|
||||
|
||||
// Update the selectedZone state
|
||||
console.log('updatedZone: ', updatedZone);
|
||||
setSelectedZone(updatedZone);
|
||||
} else {
|
||||
// If the panel is not active, activate it
|
||||
@@ -121,6 +122,7 @@ const AddButtons: React.FC<ButtonsProps> = ({
|
||||
};
|
||||
|
||||
// Update the selectedZone state
|
||||
console.log('updatedZone: ', updatedZone);
|
||||
setSelectedZone(updatedZone);
|
||||
}
|
||||
};
|
||||
@@ -129,7 +131,6 @@ const AddButtons: React.FC<ButtonsProps> = ({
|
||||
<div>
|
||||
{(["top", "right", "bottom", "left"] as Side[]).map((side) => (
|
||||
<div key={side} className={`side-button-container ${side}`}>
|
||||
{/* "+" Button */}
|
||||
<button
|
||||
className={`side-button ${side}`}
|
||||
onClick={() => handlePlusButtonClick(side)}
|
||||
@@ -147,15 +148,16 @@ const AddButtons: React.FC<ButtonsProps> = ({
|
||||
<div className="extra-Bs">
|
||||
{/* Hide Panel */}
|
||||
<div
|
||||
className={`icon ${
|
||||
hiddenPanels.includes(side) ? "active" : ""
|
||||
}`}
|
||||
className={`icon ${hiddenPanels.includes(side) ? "active" : ""
|
||||
}`}
|
||||
title={
|
||||
hiddenPanels.includes(side) ? "Show Panel" : "Hide Panel"
|
||||
}
|
||||
onClick={() => toggleVisibility(side)}
|
||||
>
|
||||
<EyeIcon />
|
||||
<EyeIcon
|
||||
fill={hiddenPanels.includes(side) ? "white" : "#1D1E21"}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Clean Panel */}
|
||||
@@ -169,9 +171,8 @@ const AddButtons: React.FC<ButtonsProps> = ({
|
||||
|
||||
{/* Lock/Unlock Panel */}
|
||||
<div
|
||||
className={`icon ${
|
||||
selectedZone.lockedPanels.includes(side) ? "active" : ""
|
||||
}`}
|
||||
className={`icon ${selectedZone.lockedPanels.includes(side) ? "active" : ""
|
||||
}`}
|
||||
title={
|
||||
selectedZone.lockedPanels.includes(side)
|
||||
? "Unlock Panel"
|
||||
@@ -179,7 +180,7 @@ const AddButtons: React.FC<ButtonsProps> = ({
|
||||
}
|
||||
onClick={() => toggleLockPanel(side)}
|
||||
>
|
||||
<LockIcon />
|
||||
<LockIcon fill={selectedZone.lockedPanels.includes(side) ? "#ffffff" : "#1D1E21"} />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -1,179 +1,187 @@
|
||||
import React, { useEffect, useRef } from "react";
|
||||
import { Widget } from "../../../store/useWidgetStore";
|
||||
|
||||
// Define the type for `Side`
|
||||
type Side = "top" | "bottom" | "left" | "right";
|
||||
|
||||
interface DisplayZoneProps {
|
||||
zonesData: {
|
||||
[key: string]: {
|
||||
activeSides: Side[];
|
||||
panelOrder: Side[];
|
||||
lockedPanels: Side[];
|
||||
widgets: Widget[];
|
||||
};
|
||||
};
|
||||
selectedZone: {
|
||||
zoneName: string;
|
||||
activeSides: Side[];
|
||||
panelOrder: Side[];
|
||||
lockedPanels: Side[];
|
||||
widgets: {
|
||||
id: string;
|
||||
type: string;
|
||||
title: string;
|
||||
panel: Side;
|
||||
data: any;
|
||||
}[];
|
||||
};
|
||||
setSelectedZone: React.Dispatch<
|
||||
React.SetStateAction<{
|
||||
zoneName: string;
|
||||
activeSides: Side[];
|
||||
panelOrder: Side[];
|
||||
lockedPanels: Side[];
|
||||
widgets: {
|
||||
id: string;
|
||||
type: string;
|
||||
title: string;
|
||||
panel: Side;
|
||||
data: any;
|
||||
}[];
|
||||
}>
|
||||
>;
|
||||
}
|
||||
|
||||
const DisplayZone: React.FC<DisplayZoneProps> = ({
|
||||
zonesData,
|
||||
selectedZone,
|
||||
setSelectedZone,
|
||||
}) => {
|
||||
// Ref for the container element
|
||||
const containerRef = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
// Example state for selectedOption and options (adjust based on your actual use case)
|
||||
const [selectedOption, setSelectedOption] = React.useState<string | null>(
|
||||
null
|
||||
);
|
||||
console.log('setSelectedOption: ', setSelectedOption);
|
||||
const [options, setOptions] = React.useState<string[]>([]);
|
||||
console.log('setOptions: ', setOptions);
|
||||
|
||||
// Scroll to the selected option when it changes
|
||||
useEffect(() => {
|
||||
const container = containerRef.current;
|
||||
|
||||
if (container && selectedOption) {
|
||||
// Handle scrolling to the selected option
|
||||
const index = options.findIndex((option) => {
|
||||
const formattedOption = formatOptionName(option);
|
||||
const selectedFormattedOption =
|
||||
selectedOption?.split("_")[1] || selectedOption;
|
||||
return formattedOption === selectedFormattedOption;
|
||||
});
|
||||
|
||||
if (index !== -1) {
|
||||
const optionElement = container.children[index] as HTMLElement;
|
||||
if (optionElement) {
|
||||
optionElement.scrollIntoView({
|
||||
behavior: "smooth",
|
||||
block: "nearest",
|
||||
inline: "center",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [selectedOption, options]);
|
||||
|
||||
useEffect(() => {
|
||||
const container = containerRef.current;
|
||||
|
||||
const handleWheel = (event: WheelEvent) => {
|
||||
event.preventDefault();
|
||||
if (container) {
|
||||
container.scrollBy({
|
||||
left: event.deltaY * 2, // Adjust the multiplier for faster scrolling
|
||||
behavior: "smooth",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
let isDragging = false;
|
||||
let startX: number;
|
||||
let scrollLeft: number;
|
||||
|
||||
const handleMouseDown = (event: MouseEvent) => {
|
||||
isDragging = true;
|
||||
startX = event.pageX - (container?.offsetLeft || 0);
|
||||
scrollLeft = container?.scrollLeft || 0;
|
||||
};
|
||||
|
||||
const handleMouseMove = (event: MouseEvent) => {
|
||||
if (!isDragging || !container) return;
|
||||
event.preventDefault();
|
||||
const x = event.pageX - (container.offsetLeft || 0);
|
||||
const walk = (x - startX) * 2; // Adjust the multiplier for faster dragging
|
||||
container.scrollLeft = scrollLeft - walk;
|
||||
};
|
||||
|
||||
const handleMouseUp = () => {
|
||||
isDragging = false;
|
||||
};
|
||||
|
||||
const handleMouseLeave = () => {
|
||||
isDragging = false;
|
||||
};
|
||||
|
||||
if (container) {
|
||||
container.addEventListener("wheel", handleWheel, { passive: false });
|
||||
container.addEventListener("mousedown", handleMouseDown);
|
||||
container.addEventListener("mousemove", handleMouseMove);
|
||||
container.addEventListener("mouseup", handleMouseUp);
|
||||
container.addEventListener("mouseleave", handleMouseLeave);
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (container) {
|
||||
container.removeEventListener("wheel", handleWheel);
|
||||
container.removeEventListener("mousedown", handleMouseDown);
|
||||
container.removeEventListener("mousemove", handleMouseMove);
|
||||
container.removeEventListener("mouseup", handleMouseUp);
|
||||
container.removeEventListener("mouseleave", handleMouseLeave);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
// Helper function to format option names (customize as needed)
|
||||
const formatOptionName = (option: string): string => {
|
||||
// Replace underscores with spaces and capitalize the first letter
|
||||
return option.replace(/_/g, " ").replace(/^\w/, (c) => c.toUpperCase());
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={containerRef}
|
||||
className={`zoon-wrapper ${
|
||||
selectedZone.activeSides.includes("bottom") && "bottom"
|
||||
}`}
|
||||
>
|
||||
{Object.keys(zonesData).map((zoneName, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className={`zone ${
|
||||
selectedZone.zoneName === zoneName ? "active" : ""
|
||||
}`}
|
||||
onClick={() => {
|
||||
setSelectedZone({
|
||||
zoneName,
|
||||
...zonesData[zoneName],
|
||||
});
|
||||
}}
|
||||
>
|
||||
{zoneName}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
import React, { useEffect, useRef } from "react";
|
||||
import { Widget } from "../../../store/useWidgetStore";
|
||||
|
||||
// Define the type for `Side`
|
||||
type Side = "top" | "bottom" | "left" | "right";
|
||||
|
||||
interface DisplayZoneProps {
|
||||
zonesData: {
|
||||
[key: string]: {
|
||||
activeSides: Side[];
|
||||
panelOrder: Side[];
|
||||
lockedPanels: Side[];
|
||||
widgets: Widget[];
|
||||
};
|
||||
};
|
||||
selectedZone: {
|
||||
zoneName: string;
|
||||
activeSides: Side[];
|
||||
panelOrder: Side[];
|
||||
lockedPanels: Side[];
|
||||
widgets: {
|
||||
id: string;
|
||||
type: string;
|
||||
title: string;
|
||||
panel: Side;
|
||||
data: any;
|
||||
}[];
|
||||
};
|
||||
setSelectedZone: React.Dispatch<
|
||||
React.SetStateAction<{
|
||||
zoneName: string;
|
||||
activeSides: Side[];
|
||||
panelOrder: Side[];
|
||||
lockedPanels: Side[];
|
||||
widgets: {
|
||||
id: string;
|
||||
type: string;
|
||||
title: string;
|
||||
panel: Side;
|
||||
data: any;
|
||||
}[];
|
||||
}>
|
||||
>;
|
||||
}
|
||||
|
||||
const DisplayZone: React.FC<DisplayZoneProps> = ({
|
||||
zonesData,
|
||||
selectedZone,
|
||||
setSelectedZone,
|
||||
}) => {
|
||||
// Ref for the container element
|
||||
const containerRef = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
// Example state for selectedOption and options (adjust based on your actual use case)
|
||||
const [selectedOption, setSelectedOption] = React.useState<string | null>(
|
||||
null
|
||||
);
|
||||
// console.log('setSelectedOption: ', setSelectedOption);
|
||||
const [options, setOptions] = React.useState<string[]>([]);
|
||||
// console.log('setOptions: ', setOptions);
|
||||
|
||||
// Scroll to the selected option when it changes
|
||||
useEffect(() => {
|
||||
const container = containerRef.current;
|
||||
|
||||
if (container && selectedOption) {
|
||||
// Handle scrolling to the selected option
|
||||
const index = options.findIndex((option) => {
|
||||
const formattedOption = formatOptionName(option);
|
||||
const selectedFormattedOption =
|
||||
selectedOption?.split("_")[1] || selectedOption;
|
||||
return formattedOption === selectedFormattedOption;
|
||||
});
|
||||
|
||||
if (index !== -1) {
|
||||
const optionElement = container.children[index] as HTMLElement;
|
||||
if (optionElement) {
|
||||
optionElement.scrollIntoView({
|
||||
behavior: "smooth",
|
||||
block: "nearest",
|
||||
inline: "center",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [selectedOption, options]);
|
||||
|
||||
useEffect(() => {
|
||||
const container = containerRef.current;
|
||||
|
||||
const handleWheel = (event: WheelEvent) => {
|
||||
event.preventDefault();
|
||||
if (container) {
|
||||
container.scrollBy({
|
||||
left: event.deltaY * 2, // Adjust the multiplier for faster scrolling
|
||||
behavior: "smooth",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
let isDragging = false;
|
||||
let startX: number;
|
||||
let scrollLeft: number;
|
||||
|
||||
const handleMouseDown = (event: MouseEvent) => {
|
||||
isDragging = true;
|
||||
startX = event.pageX - (container?.offsetLeft || 0);
|
||||
scrollLeft = container?.scrollLeft || 0;
|
||||
};
|
||||
|
||||
const handleMouseMove = (event: MouseEvent) => {
|
||||
if (!isDragging || !container) return;
|
||||
event.preventDefault();
|
||||
const x = event.pageX - (container.offsetLeft || 0);
|
||||
const walk = (x - startX) * 2; // Adjust the multiplier for faster dragging
|
||||
container.scrollLeft = scrollLeft - walk;
|
||||
};
|
||||
|
||||
const handleMouseUp = () => {
|
||||
isDragging = false;
|
||||
};
|
||||
|
||||
const handleMouseLeave = () => {
|
||||
isDragging = false;
|
||||
};
|
||||
|
||||
if (container) {
|
||||
container.addEventListener("wheel", handleWheel, { passive: false });
|
||||
container.addEventListener("mousedown", handleMouseDown);
|
||||
container.addEventListener("mousemove", handleMouseMove);
|
||||
container.addEventListener("mouseup", handleMouseUp);
|
||||
container.addEventListener("mouseleave", handleMouseLeave);
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (container) {
|
||||
container.removeEventListener("wheel", handleWheel);
|
||||
container.removeEventListener("mousedown", handleMouseDown);
|
||||
container.removeEventListener("mousemove", handleMouseMove);
|
||||
container.removeEventListener("mouseup", handleMouseUp);
|
||||
container.removeEventListener("mouseleave", handleMouseLeave);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
// Helper function to format option names (customize as needed)
|
||||
const formatOptionName = (option: string): string => {
|
||||
// Replace underscores with spaces and capitalize the first letter
|
||||
return option.replace(/_/g, " ").replace(/^\w/, (c) => c.toUpperCase());
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={containerRef}
|
||||
className={`zoon-wrapper ${selectedZone.activeSides.includes("bottom") && "bottom"
|
||||
}`}
|
||||
>
|
||||
{Object.keys(zonesData).map((zoneName, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className={`zone ${selectedZone.zoneName === zoneName ? "active" : ""
|
||||
}`}
|
||||
onClick={() => {
|
||||
console.log('zoneName: ', zoneName);
|
||||
|
||||
setSelectedZone({
|
||||
zoneName,
|
||||
activeSides: zonesData[zoneName].activeSides || [],
|
||||
panelOrder: zonesData[zoneName].panelOrder || [],
|
||||
lockedPanels: zonesData[zoneName].lockedPanels || [],
|
||||
widgets: zonesData[zoneName].widgets || [],
|
||||
})
|
||||
// setSelectedZone({
|
||||
// zoneName,
|
||||
// ...zonesData[zoneName],
|
||||
// });
|
||||
console.log(selectedZone);
|
||||
}}
|
||||
>
|
||||
{zoneName}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default DisplayZone;
|
||||
@@ -1,9 +1,22 @@
|
||||
import { useWidgetStore } from "../../../store/useWidgetStore";
|
||||
import PieGraphComponent from "../charts/PieGraphComponent";
|
||||
import BarGraphComponent from "../charts/BarGraphComponent";
|
||||
import LineGraphComponent from "../charts/LineGraphComponent";
|
||||
import ProgressCard from "../realTimeVis/charts/ProgressCard";
|
||||
import PieGraphComponent from "../realTimeVis/charts/PieGraphComponent";
|
||||
import BarGraphComponent from "../realTimeVis/charts/BarGraphComponent";
|
||||
import LineGraphComponent from "../realTimeVis/charts/LineGraphComponent";
|
||||
import RadarGraphComponent from "../realTimeVis/charts/RadarGraphComponent";
|
||||
import DoughnutGraphComponent from "../realTimeVis/charts/DoughnutGraphComponent";
|
||||
import PolarAreaGraphComponent from "../realTimeVis/charts/PolarAreaGraphComponent";
|
||||
|
||||
export const DraggableWidget = ({
|
||||
widget,
|
||||
hiddenPanels, // Add this prop to track hidden panels
|
||||
index, onReorder
|
||||
}: {
|
||||
widget: any;
|
||||
hiddenPanels: string[]; // Array of hidden panel names
|
||||
index: number; onReorder: (fromIndex: number, toIndex: number) => void
|
||||
}) => {
|
||||
|
||||
export const DraggableWidget = ({ widget }: { widget: any }) => {
|
||||
const { selectedChartId, setSelectedChartId } = useWidgetStore();
|
||||
|
||||
const handlePointerDown = () => {
|
||||
@@ -12,69 +25,139 @@ export const DraggableWidget = ({ widget }: { widget: any }) => {
|
||||
}
|
||||
};
|
||||
|
||||
// Determine if the widget's panel is hidden
|
||||
const isPanelHidden = hiddenPanels.includes(widget.panel);
|
||||
|
||||
const handleDragStart = (event: React.DragEvent<HTMLDivElement>) => {
|
||||
event.dataTransfer.setData('text/plain', index.toString()); // Store the index of the dragged widget
|
||||
};
|
||||
const handleDragEnter = (event: React.DragEvent<HTMLDivElement>) => {
|
||||
event.preventDefault(); // Allow drop
|
||||
};
|
||||
|
||||
const handleDragOver = (event: React.DragEvent<HTMLDivElement>) => {
|
||||
event.preventDefault(); // Allow drop
|
||||
};
|
||||
|
||||
const handleDrop = (event: React.DragEvent<HTMLDivElement>) => {
|
||||
event.preventDefault();
|
||||
const fromIndex = parseInt(event.dataTransfer.getData('text/plain'), 10); // Get the dragged widget's index
|
||||
const toIndex = index; // The index of the widget where the drop occurred
|
||||
if (fromIndex !== toIndex) {
|
||||
onReorder(fromIndex, toIndex); // Call the reorder function passed as a prop
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
draggable
|
||||
key={widget.id}
|
||||
className={`chart-container ${
|
||||
selectedChartId?.id === widget.id && "activeChart"
|
||||
}`}
|
||||
className={`chart-container ${selectedChartId?.id === widget.id && "activeChart"
|
||||
}`}
|
||||
onPointerDown={handlePointerDown}
|
||||
onDragStart={handleDragStart}
|
||||
onDragEnter={handleDragEnter}
|
||||
onDragOver={handleDragOver}
|
||||
onDrop={handleDrop}
|
||||
style={{
|
||||
opacity: isPanelHidden ? 0 : 1, // Set opacity to 0 if the panel is hidden
|
||||
pointerEvents: isPanelHidden ? "none" : "auto", // Disable interaction when hidden
|
||||
}}
|
||||
>
|
||||
{widget.type === "progress" ? (
|
||||
// <ProgressCard title={widget.title} data={widget.data} />
|
||||
<></>
|
||||
) : (
|
||||
<>
|
||||
{widget.type === "line" && (
|
||||
<LineGraphComponent
|
||||
type={widget.type}
|
||||
title={widget.title}
|
||||
fontSize={widget.fontSize}
|
||||
fontWeight={widget.fontWeight}
|
||||
data={{
|
||||
measurements: [
|
||||
{ name: "testDevice", fields: "powerConsumption" },
|
||||
{ name: "furnace", fields: "powerConsumption" },
|
||||
],
|
||||
interval: 1000,
|
||||
duration: "1h",
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{widget.type === "bar" && (
|
||||
<BarGraphComponent
|
||||
type={widget.type}
|
||||
title={widget.title}
|
||||
fontSize={widget.fontSize}
|
||||
fontWeight={widget.fontWeight}
|
||||
data={{
|
||||
measurements: [
|
||||
{ name: "testDevice", fields: "powerConsumption" },
|
||||
{ name: "furnace", fields: "powerConsumption" },
|
||||
],
|
||||
interval: 1000,
|
||||
duration: "1h",
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{widget.type === "pie" && (
|
||||
<PieGraphComponent
|
||||
type={widget.type}
|
||||
title={widget.title}
|
||||
fontSize={widget.fontSize}
|
||||
fontWeight={widget.fontWeight}
|
||||
data={{
|
||||
measurements: [
|
||||
{ name: "testDevice", fields: "powerConsumption" },
|
||||
{ name: "furnace", fields: "powerConsumption" },
|
||||
],
|
||||
interval: 1000,
|
||||
duration: "1h",
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
{/* Render charts based on widget type */}
|
||||
{widget.type === "progress" && (
|
||||
<ProgressCard title={widget.title} data={widget.data} />
|
||||
)}
|
||||
{widget.type === "line" && (
|
||||
<LineGraphComponent
|
||||
type={widget.type}
|
||||
title={widget.title}
|
||||
fontSize={widget.fontSize}
|
||||
fontWeight={widget.fontWeight}
|
||||
data={{
|
||||
measurements: [
|
||||
{ name: "testDevice", fields: "powerConsumption" },
|
||||
{ name: "furnace", fields: "powerConsumption" },
|
||||
],
|
||||
interval: 1000,
|
||||
duration: "1h",
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{widget.type === "bar" && (
|
||||
<BarGraphComponent
|
||||
type={widget.type}
|
||||
title={widget.title}
|
||||
fontSize={widget.fontSize}
|
||||
fontWeight={widget.fontWeight}
|
||||
data={{
|
||||
measurements: [
|
||||
{ name: "testDevice", fields: "powerConsumption" },
|
||||
{ name: "furnace", fields: "powerConsumption" },
|
||||
],
|
||||
interval: 1000,
|
||||
duration: "1h",
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{/* {widget.type === "radar" && (
|
||||
<RadarGraphComponent
|
||||
type={widget.type}
|
||||
title={widget.title}
|
||||
fontSize={widget.fontSize}
|
||||
fontWeight={widget.fontWeight}
|
||||
data={widget.data.measurements.map((item: any) => item.fields)}
|
||||
/>
|
||||
)} */}
|
||||
{widget.type === "pie" && (
|
||||
<PieGraphComponent
|
||||
type={widget.type}
|
||||
title={widget.title}
|
||||
fontSize={widget.fontSize}
|
||||
fontWeight={widget.fontWeight}
|
||||
data={{
|
||||
measurements: [
|
||||
{ name: "testDevice", fields: "powerConsumption" },
|
||||
{ name: "furnace", fields: "powerConsumption" },
|
||||
],
|
||||
interval: 1000,
|
||||
duration: "1h",
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{widget.type === "doughnut" && (
|
||||
<DoughnutGraphComponent
|
||||
type={widget.type}
|
||||
title={widget.title}
|
||||
fontSize={widget.fontSize}
|
||||
fontWeight={widget.fontWeight}
|
||||
data={{
|
||||
measurements: [
|
||||
{ name: "testDevice", fields: "powerConsumption" },
|
||||
{ name: "furnace", fields: "powerConsumption" },
|
||||
],
|
||||
interval: 1000,
|
||||
duration: "1h",
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{widget.type === "polarArea" && (
|
||||
<PolarAreaGraphComponent
|
||||
type={widget.type}
|
||||
title={widget.title}
|
||||
fontSize={widget.fontSize}
|
||||
fontWeight={widget.fontWeight}
|
||||
data={{
|
||||
measurements: [
|
||||
{ name: "testDevice", fields: "powerConsumption" },
|
||||
{ name: "furnace", fields: "powerConsumption" },
|
||||
],
|
||||
interval: 1000,
|
||||
duration: "1h",
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
|
||||
@@ -2,6 +2,7 @@ import React, { useEffect, useMemo, useRef, useState } from "react";
|
||||
import { useWidgetStore } from "../../../store/useWidgetStore";
|
||||
import { usePlayButtonStore } from "../../../store/usePlayButtonStore";
|
||||
import { DraggableWidget } from "./DraggableWidget";
|
||||
import { arrayMove } from "@dnd-kit/sortable";
|
||||
|
||||
type Side = "top" | "bottom" | "left" | "right";
|
||||
|
||||
@@ -30,17 +31,24 @@ interface PanelProps {
|
||||
widgets: Widget[];
|
||||
}>
|
||||
>;
|
||||
hiddenPanels: string[];
|
||||
}
|
||||
|
||||
const generateUniqueId = () =>
|
||||
`${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
||||
|
||||
const Panel: React.FC<PanelProps> = ({ selectedZone, setSelectedZone }) => {
|
||||
const Panel: React.FC<PanelProps> = ({
|
||||
selectedZone,
|
||||
setSelectedZone,
|
||||
hiddenPanels,
|
||||
}) => {
|
||||
const panelRefs = useRef<{ [side in Side]?: HTMLDivElement }>({});
|
||||
const [panelDimensions, setPanelDimensions] = useState<{
|
||||
[side in Side]?: { width: number; height: number };
|
||||
}>({});
|
||||
|
||||
const { isPlaying } = usePlayButtonStore();
|
||||
|
||||
const getPanelStyle = useMemo(
|
||||
() => (side: Side) => {
|
||||
const currentIndex = selectedZone.panelOrder.indexOf(side);
|
||||
@@ -49,15 +57,14 @@ const Panel: React.FC<PanelProps> = ({ selectedZone, setSelectedZone }) => {
|
||||
const rightActive = previousPanels.includes("right");
|
||||
const topActive = previousPanels.includes("top");
|
||||
const bottomActive = previousPanels.includes("bottom");
|
||||
const panelSize = isPlaying ? 355 : 305;
|
||||
const panelSize = isPlaying ? 300 : 210;
|
||||
|
||||
switch (side) {
|
||||
case "top":
|
||||
case "bottom":
|
||||
return {
|
||||
width: `calc(100% - ${
|
||||
(leftActive ? panelSize : 0) + (rightActive ? panelSize : 0)
|
||||
}px)`,
|
||||
width: `calc(100% - ${(leftActive ? panelSize : 0) + (rightActive ? panelSize : 0)
|
||||
}px)`,
|
||||
height: `${panelSize - 5}px`,
|
||||
left: leftActive ? `${panelSize}px` : "0",
|
||||
right: rightActive ? `${panelSize}px` : "0",
|
||||
@@ -67,9 +74,8 @@ const Panel: React.FC<PanelProps> = ({ selectedZone, setSelectedZone }) => {
|
||||
case "right":
|
||||
return {
|
||||
width: `${panelSize - 5}px`,
|
||||
height: `calc(100% - ${
|
||||
(topActive ? panelSize : 0) + (bottomActive ? panelSize : 0)
|
||||
}px)`,
|
||||
height: `calc(100% - ${(topActive ? panelSize : 0) + (bottomActive ? panelSize : 0)
|
||||
}px)`,
|
||||
top: topActive ? `${panelSize}px` : "0",
|
||||
bottom: bottomActive ? `${panelSize}px` : "0",
|
||||
[side]: "0",
|
||||
@@ -78,7 +84,7 @@ const Panel: React.FC<PanelProps> = ({ selectedZone, setSelectedZone }) => {
|
||||
return {};
|
||||
}
|
||||
},
|
||||
[selectedZone.panelOrder]
|
||||
[selectedZone.panelOrder, isPlaying]
|
||||
);
|
||||
|
||||
const handleDrop = (e: React.DragEvent, panel: Side) => {
|
||||
@@ -92,6 +98,8 @@ const Panel: React.FC<PanelProps> = ({ selectedZone, setSelectedZone }) => {
|
||||
|
||||
if (currentWidgetsCount >= maxCapacity) return;
|
||||
|
||||
console.log('draggedAsset: ', draggedAsset);
|
||||
console.log('panel: ', panel);
|
||||
addWidgetToPanel(draggedAsset, panel);
|
||||
};
|
||||
|
||||
@@ -102,8 +110,8 @@ const Panel: React.FC<PanelProps> = ({ selectedZone, setSelectedZone }) => {
|
||||
selectedZone.widgets.filter((w) => w.panel === panel).length;
|
||||
|
||||
const calculatePanelCapacity = (panel: Side) => {
|
||||
const CHART_WIDTH = 250;
|
||||
const CHART_HEIGHT = 250;
|
||||
const CHART_WIDTH = 150;
|
||||
const CHART_HEIGHT = 150;
|
||||
const FALLBACK_HORIZONTAL_CAPACITY = 5;
|
||||
const FALLBACK_VERTICAL_CAPACITY = 3;
|
||||
|
||||
@@ -158,8 +166,34 @@ const Panel: React.FC<PanelProps> = ({ selectedZone, setSelectedZone }) => {
|
||||
};
|
||||
}, [selectedZone.activeSides]);
|
||||
|
||||
const { isPlaying } = usePlayButtonStore();
|
||||
const handleReorder = (fromIndex: number, toIndex: number, panel: Side) => {
|
||||
if (!selectedZone) return; // Ensure selectedZone is not null
|
||||
console.log('selectedZone: ', selectedZone);
|
||||
|
||||
setSelectedZone((prev) => {
|
||||
if (!prev) return prev; // Ensure prev is not null
|
||||
|
||||
// Filter widgets for the specified panel
|
||||
const widgetsInPanel = prev.widgets.filter((w) => w.panel === panel);
|
||||
|
||||
// Reorder widgets within the same panel
|
||||
const reorderedWidgets = arrayMove(widgetsInPanel, fromIndex, toIndex);
|
||||
|
||||
// Merge the reordered widgets back into the full list while preserving the order
|
||||
const updatedWidgets = prev.widgets
|
||||
.filter((widget) => widget.panel !== panel) // Keep widgets from other panels
|
||||
.concat(reorderedWidgets); // Add the reordered widgets for the specified panel
|
||||
|
||||
return {
|
||||
...prev,
|
||||
widgets: updatedWidgets,
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
{selectedZone.activeSides.map((side) => (
|
||||
@@ -188,8 +222,16 @@ const Panel: React.FC<PanelProps> = ({ selectedZone, setSelectedZone }) => {
|
||||
>
|
||||
{selectedZone.widgets
|
||||
.filter((w) => w.panel === side)
|
||||
.map((widget) => (
|
||||
<DraggableWidget widget={widget} key={widget.id} />
|
||||
.map((widget, index) => (
|
||||
<DraggableWidget
|
||||
hiddenPanels={hiddenPanels}
|
||||
widget={widget}
|
||||
key={widget.id}
|
||||
index={index}
|
||||
onReorder={(fromIndex, toIndex) =>
|
||||
handleReorder(fromIndex, toIndex, side)
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
@@ -198,4 +240,6 @@ const Panel: React.FC<PanelProps> = ({ selectedZone, setSelectedZone }) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default Panel;
|
||||
export default Panel;
|
||||
|
||||
|
||||
|
||||
@@ -4,100 +4,144 @@ import Panel from "./Panel";
|
||||
import AddButtons from "./AddButtons";
|
||||
import { useSelectedZoneStore } from "../../../store/useZoneStore";
|
||||
import DisplayZone from "./DisplayZone";
|
||||
import Scene from "../../../modules/scene/scene";
|
||||
import useModuleStore from "../../../store/useModuleStore";
|
||||
import { getZonesApi } from "../../../services/realTimeVisulization/zoneData/getZones";
|
||||
|
||||
|
||||
type Side = "top" | "bottom" | "left" | "right";
|
||||
|
||||
interface Widget {
|
||||
type FormattedZoneData = Record<
|
||||
string,
|
||||
{
|
||||
activeSides: Side[];
|
||||
panelOrder: Side[];
|
||||
lockedPanels: Side[];
|
||||
zoneCentrePoint: number[];
|
||||
widgets: Widget[];
|
||||
}
|
||||
>;
|
||||
type Widget = {
|
||||
id: string;
|
||||
type: string;
|
||||
title: string;
|
||||
panel: Side;
|
||||
data: any;
|
||||
}
|
||||
};
|
||||
type Zone = {
|
||||
zoneId: string;
|
||||
zoneName: string;
|
||||
points: number[][];
|
||||
layer: number;
|
||||
};
|
||||
|
||||
const RealTimeVisulization: React.FC = () => {
|
||||
const [hiddenPanels, setHiddenPanels] = React.useState<Side[]>([]);
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const [zonesData, setZonesData] = useState<{
|
||||
[key: string]: {
|
||||
activeSides: Side[];
|
||||
panelOrder: Side[];
|
||||
lockedPanels: Side[];
|
||||
widgets: Widget[];
|
||||
};
|
||||
}>({
|
||||
"Manufacturing unit": {
|
||||
activeSides: [],
|
||||
panelOrder: [],
|
||||
lockedPanels: [],
|
||||
widgets: [],
|
||||
},
|
||||
"Assembly unit": {
|
||||
activeSides: [],
|
||||
panelOrder: [],
|
||||
lockedPanels: [],
|
||||
widgets: [],
|
||||
},
|
||||
"Packing unit": {
|
||||
activeSides: [],
|
||||
panelOrder: [],
|
||||
lockedPanels: [],
|
||||
widgets: [],
|
||||
},
|
||||
Warehouse: {
|
||||
activeSides: [],
|
||||
panelOrder: [],
|
||||
lockedPanels: [],
|
||||
widgets: [],
|
||||
},
|
||||
Inventory: {
|
||||
activeSides: [],
|
||||
panelOrder: [],
|
||||
lockedPanels: [],
|
||||
widgets: [],
|
||||
},
|
||||
});
|
||||
|
||||
const { isPlaying } = usePlayButtonStore();
|
||||
const { activeModule } = useModuleStore();
|
||||
|
||||
const [zonesData, setZonesData] = useState<FormattedZoneData>({});
|
||||
const { selectedZone, setSelectedZone } = useSelectedZoneStore();
|
||||
|
||||
useEffect(() => {
|
||||
setZonesData((prev) => ({
|
||||
...prev,
|
||||
[selectedZone.zoneName]: selectedZone,
|
||||
}));
|
||||
async function GetZoneData() {
|
||||
try {
|
||||
const response: { data: Zone[] } | undefined = await getZonesApi(
|
||||
"hexrfactory"
|
||||
);
|
||||
|
||||
if (!response || !response.data) {
|
||||
return;
|
||||
}
|
||||
const formattedData = response?.data?.reduce<FormattedZoneData>(
|
||||
(acc, zone) => {
|
||||
acc[zone.zoneName] = {
|
||||
activeSides: [],
|
||||
panelOrder: [],
|
||||
lockedPanels: [],
|
||||
zoneCentrePoint: [],
|
||||
widgets: [],
|
||||
};
|
||||
return acc;
|
||||
},
|
||||
{}
|
||||
);
|
||||
setZonesData(formattedData);
|
||||
} catch (error) { }
|
||||
}
|
||||
GetZoneData();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
console.log('zonesData: ', zonesData);
|
||||
}, [zonesData]);
|
||||
|
||||
useEffect(() => {
|
||||
setZonesData((prev) => {
|
||||
if (!selectedZone) return prev;
|
||||
|
||||
return {
|
||||
...prev,
|
||||
[selectedZone.zoneName]: {
|
||||
...prev[selectedZone.zoneName], // Keep existing properties
|
||||
activeSides: selectedZone.activeSides || [],
|
||||
panelOrder: selectedZone.panelOrder || [],
|
||||
lockedPanels: selectedZone.lockedPanels || [],
|
||||
widgets: selectedZone.widgets || [],
|
||||
},
|
||||
};
|
||||
});
|
||||
}, [selectedZone]);
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={containerRef}
|
||||
id="real-time-vis-canvas"
|
||||
className={`realTime-viz canvas ${!isPlaying ? "playActiveFalse" : ""}`}
|
||||
className={`realTime-viz canvas ${isPlaying ? "playingFlase" : ""}`}
|
||||
style={{
|
||||
height: isPlaying ? "100vh" : "",
|
||||
width: isPlaying ? "100%" : "",
|
||||
left: isPlaying ? "0%" : "",
|
||||
height: isPlaying || activeModule !== "visualization" ? "100vh" : "",
|
||||
width: isPlaying || activeModule !== "visualization" ? "100vw" : "",
|
||||
left: isPlaying || activeModule !== "visualization" ? "0%" : "",
|
||||
}}
|
||||
>
|
||||
<DisplayZone
|
||||
zonesData={zonesData}
|
||||
selectedZone={selectedZone}
|
||||
setSelectedZone={setSelectedZone}
|
||||
/>
|
||||
<div
|
||||
className="scene-container"
|
||||
style={{
|
||||
height: "100%",
|
||||
width: "100%",
|
||||
borderRadius: isPlaying || activeModule !== "visualization" ? "" : "6px",
|
||||
}}
|
||||
>
|
||||
<Scene />
|
||||
</div>
|
||||
{activeModule === "visualization" && (
|
||||
<>
|
||||
<DisplayZone
|
||||
zonesData={zonesData}
|
||||
selectedZone={selectedZone}
|
||||
setSelectedZone={setSelectedZone}
|
||||
/>
|
||||
|
||||
{!isPlaying && (
|
||||
<AddButtons
|
||||
hiddenPanels={hiddenPanels}
|
||||
setHiddenPanels={setHiddenPanels}
|
||||
selectedZone={selectedZone}
|
||||
setSelectedZone={setSelectedZone}
|
||||
/>
|
||||
{!isPlaying && (
|
||||
<AddButtons
|
||||
hiddenPanels={hiddenPanels}
|
||||
setHiddenPanels={setHiddenPanels}
|
||||
selectedZone={selectedZone}
|
||||
setSelectedZone={setSelectedZone}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Panel
|
||||
selectedZone={selectedZone}
|
||||
setSelectedZone={setSelectedZone}
|
||||
hiddenPanels={hiddenPanels}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
<Panel selectedZone={selectedZone} setSelectedZone={setSelectedZone} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default RealTimeVisulization;
|
||||
|
||||
|
||||
@@ -1,76 +1,76 @@
|
||||
import React, { useState } from "react";
|
||||
import RenameInput from "./RenameInput";
|
||||
|
||||
type InputWithDropDownProps = {
|
||||
label: string;
|
||||
value: string;
|
||||
options?: string[]; // Array of dropdown options
|
||||
activeOption?: string; // The currently active dropdown option
|
||||
onClick?: () => void;
|
||||
onChange: (newValue: string) => void;
|
||||
editableLabel?: boolean;
|
||||
};
|
||||
|
||||
const InputWithDropDown: React.FC<InputWithDropDownProps> = ({
|
||||
label,
|
||||
value,
|
||||
options,
|
||||
activeOption,
|
||||
onClick,
|
||||
onChange,
|
||||
editableLabel = false,
|
||||
}) => {
|
||||
const separatedWords = label
|
||||
.split(/(?=[A-Z])/)
|
||||
.map((word) => word.trim())
|
||||
.toString();
|
||||
|
||||
const [openDropdown, setOpenDropdown] = useState(false);
|
||||
|
||||
return (
|
||||
<div className="value-field-container">
|
||||
{editableLabel ? (
|
||||
<RenameInput value={label} />
|
||||
) : (
|
||||
<label htmlFor={separatedWords} className="label">
|
||||
{label}
|
||||
</label>
|
||||
)}
|
||||
<div className="input default" id={separatedWords}>
|
||||
<input
|
||||
type="text"
|
||||
defaultValue={value}
|
||||
onChange={(e) => {
|
||||
onChange(e.target.value);
|
||||
}}
|
||||
/>
|
||||
|
||||
{activeOption && (
|
||||
<div
|
||||
className="dropdown"
|
||||
onClick={() => {
|
||||
setOpenDropdown(true);
|
||||
}}
|
||||
>
|
||||
<div className="active-option">{activeOption}</div>
|
||||
{options && openDropdown && (
|
||||
<div className="dropdown-options-list">
|
||||
{options.map((option, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className={"dropdown-option"}
|
||||
onClick={onClick}
|
||||
>
|
||||
{option}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default InputWithDropDown;
|
||||
import React, { useState } from "react";
|
||||
import RenameInput from "./RenameInput";
|
||||
|
||||
type InputWithDropDownProps = {
|
||||
label: string;
|
||||
value: string;
|
||||
options?: string[]; // Array of dropdown options
|
||||
activeOption?: string; // The currently active dropdown option
|
||||
onClick?: () => void;
|
||||
onChange: (newValue: string) => void;
|
||||
editableLabel?: boolean;
|
||||
};
|
||||
|
||||
const InputWithDropDown: React.FC<InputWithDropDownProps> = ({
|
||||
label,
|
||||
value,
|
||||
options,
|
||||
activeOption,
|
||||
onClick,
|
||||
onChange,
|
||||
editableLabel = false,
|
||||
}) => {
|
||||
const separatedWords = label
|
||||
.split(/(?=[A-Z])/)
|
||||
.map((word) => word.trim())
|
||||
.toString();
|
||||
|
||||
const [openDropdown, setOpenDropdown] = useState(false);
|
||||
|
||||
return (
|
||||
<div className="value-field-container">
|
||||
{editableLabel ? (
|
||||
<RenameInput value={label} />
|
||||
) : (
|
||||
<label htmlFor={separatedWords} className="label">
|
||||
{label}
|
||||
</label>
|
||||
)}
|
||||
<div className="input default" id={separatedWords}>
|
||||
<input
|
||||
type="text"
|
||||
defaultValue={value}
|
||||
onChange={(e) => {
|
||||
onChange(e.target.value);
|
||||
}}
|
||||
/>
|
||||
|
||||
{activeOption && (
|
||||
<div
|
||||
className="dropdown"
|
||||
onClick={() => {
|
||||
setOpenDropdown(true);
|
||||
}}
|
||||
>
|
||||
<div className="active-option">{activeOption}</div>
|
||||
{options && openDropdown && (
|
||||
<div className="dropdown-options-list">
|
||||
{options.map((option, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className={"dropdown-option"}
|
||||
onClick={onClick}
|
||||
>
|
||||
{option}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default InputWithDropDown;
|
||||
|
||||
@@ -1,29 +1,29 @@
|
||||
import React, { useState } from "react";
|
||||
import RegularDropDown from "./RegularDropDown";
|
||||
|
||||
type LabledDropdownProps = {
|
||||
defaultOption: string; // Initial active option
|
||||
options: string[]; // Array of dropdown options
|
||||
};
|
||||
|
||||
const LabledDropdown: React.FC<LabledDropdownProps> = ({ defaultOption, options }) => {
|
||||
const [activeOption, setActiveOption] = useState(defaultOption); // State for active option
|
||||
|
||||
const handleSelect = (option: string) => {
|
||||
setActiveOption(option); // Update the active option state
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="value-field-container">
|
||||
<div className="label">Type</div>
|
||||
<RegularDropDown
|
||||
header={activeOption} // Display the current active option
|
||||
options={options} // Use the options from props
|
||||
onSelect={handleSelect} // Handle option selection
|
||||
search = {false}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default LabledDropdown;
|
||||
import React, { useState } from "react";
|
||||
import RegularDropDown from "./RegularDropDown";
|
||||
|
||||
type LabledDropdownProps = {
|
||||
defaultOption: string; // Initial active option
|
||||
options: string[]; // Array of dropdown options
|
||||
};
|
||||
|
||||
const LabledDropdown: React.FC<LabledDropdownProps> = ({ defaultOption, options }) => {
|
||||
const [activeOption, setActiveOption] = useState(defaultOption); // State for active option
|
||||
|
||||
const handleSelect = (option: string) => {
|
||||
setActiveOption(option); // Update the active option state
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="value-field-container">
|
||||
<div className="label">Type</div>
|
||||
<RegularDropDown
|
||||
header={activeOption} // Display the current active option
|
||||
options={options} // Use the options from props
|
||||
onSelect={handleSelect} // Handle option selection
|
||||
search = {false}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default LabledDropdown;
|
||||
|
||||
@@ -1,141 +1,141 @@
|
||||
import React, { useState, useRef, useEffect } from "react";
|
||||
|
||||
// Dropdown Item Component
|
||||
const DropdownItem = ({
|
||||
label,
|
||||
href,
|
||||
onClick,
|
||||
}: {
|
||||
label: string;
|
||||
href?: string;
|
||||
onClick?: () => void;
|
||||
}) => (
|
||||
<a
|
||||
href={href || "#"}
|
||||
className="dropdown-item"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
onClick?.();
|
||||
}}
|
||||
>
|
||||
{label}
|
||||
</a>
|
||||
);
|
||||
|
||||
// Nested Dropdown Component
|
||||
const NestedDropdown = ({
|
||||
label,
|
||||
children,
|
||||
onSelect,
|
||||
}: {
|
||||
label: string;
|
||||
children: React.ReactNode;
|
||||
onSelect: (selectedLabel: string) => void;
|
||||
}) => {
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<div className="nested-dropdown">
|
||||
{/* Dropdown Trigger */}
|
||||
<div
|
||||
className={`dropdown-trigger ${open ? "open" : ""}`}
|
||||
onClick={() => setOpen(!open)} // Toggle submenu on click
|
||||
>
|
||||
{label} <span className="icon">{open ? "▼" : "▶"}</span>
|
||||
</div>
|
||||
|
||||
{/* Submenu */}
|
||||
{open && (
|
||||
<div className="submenu">
|
||||
{React.Children.map(children, (child) => {
|
||||
if (React.isValidElement(child)) {
|
||||
// Clone the element and pass the `onSelect` prop only if it's expected
|
||||
return React.cloneElement(child as React.ReactElement<any>, { onSelect });
|
||||
}
|
||||
return child; // Return non-element children as-is
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// Recursive Function to Render Nested Data
|
||||
const renderNestedData = (
|
||||
data: Record<string, any>,
|
||||
onSelect: (selectedLabel: string) => void
|
||||
) => {
|
||||
return Object.entries(data).map(([key, value]) => {
|
||||
if (typeof value === "object" && !Array.isArray(value)) {
|
||||
// If the value is an object, render it as a nested dropdown
|
||||
return (
|
||||
<NestedDropdown key={key} label={key} onSelect={onSelect}>
|
||||
{renderNestedData(value, onSelect)}
|
||||
</NestedDropdown>
|
||||
);
|
||||
} else if (Array.isArray(value)) {
|
||||
// If the value is an array, render each item as a dropdown item
|
||||
return value.map((item, index) => (
|
||||
<DropdownItem key={index} label={item} onClick={() => onSelect(item)} />
|
||||
));
|
||||
} else {
|
||||
// If the value is a simple string, render it as a dropdown item
|
||||
return (
|
||||
<DropdownItem key={key} label={value} onClick={() => onSelect(value)} />
|
||||
);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// Main Multi-Level Dropdown Component
|
||||
const MultiLevelDropdown = ({ data }: { data: Record<string, any> }) => {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [selectedLabel, setSelectedLabel] = useState("Dropdown trigger");
|
||||
const dropdownRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
// Handle outer click to close the dropdown
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
if (
|
||||
dropdownRef.current &&
|
||||
!dropdownRef.current.contains(event.target as Node)
|
||||
) {
|
||||
setOpen(false);
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener("mousedown", handleClickOutside);
|
||||
return () => {
|
||||
document.removeEventListener("mousedown", handleClickOutside);
|
||||
};
|
||||
}, []);
|
||||
|
||||
// Handle selection of an item
|
||||
const handleSelect = (selectedLabel: string) => {
|
||||
setSelectedLabel(selectedLabel); // Update the dropdown trigger text
|
||||
setOpen(false); // Close the dropdown
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="multi-level-dropdown" ref={dropdownRef}>
|
||||
{/* Dropdown Trigger Button */}
|
||||
<button
|
||||
className={`dropdown-button ${open ? "open" : ""}`}
|
||||
onClick={() => setOpen(!open)} // Toggle main menu on click
|
||||
>
|
||||
{selectedLabel} <span className="icon">▾</span>
|
||||
</button>
|
||||
|
||||
{/* Dropdown Menu */}
|
||||
{open && (
|
||||
<div className="dropdown-menu">
|
||||
<div className="dropdown-content">
|
||||
{renderNestedData(data, handleSelect)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
import React, { useState, useRef, useEffect } from "react";
|
||||
|
||||
// Dropdown Item Component
|
||||
const DropdownItem = ({
|
||||
label,
|
||||
href,
|
||||
onClick,
|
||||
}: {
|
||||
label: string;
|
||||
href?: string;
|
||||
onClick?: () => void;
|
||||
}) => (
|
||||
<a
|
||||
href={href || "#"}
|
||||
className="dropdown-item"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
onClick?.();
|
||||
}}
|
||||
>
|
||||
{label}
|
||||
</a>
|
||||
);
|
||||
|
||||
// Nested Dropdown Component
|
||||
const NestedDropdown = ({
|
||||
label,
|
||||
children,
|
||||
onSelect,
|
||||
}: {
|
||||
label: string;
|
||||
children: React.ReactNode;
|
||||
onSelect: (selectedLabel: string) => void;
|
||||
}) => {
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<div className="nested-dropdown">
|
||||
{/* Dropdown Trigger */}
|
||||
<div
|
||||
className={`dropdown-trigger ${open ? "open" : ""}`}
|
||||
onClick={() => setOpen(!open)} // Toggle submenu on click
|
||||
>
|
||||
{label} <span className="icon">{open ? "▼" : "▶"}</span>
|
||||
</div>
|
||||
|
||||
{/* Submenu */}
|
||||
{open && (
|
||||
<div className="submenu">
|
||||
{React.Children.map(children, (child) => {
|
||||
if (React.isValidElement(child)) {
|
||||
// Clone the element and pass the `onSelect` prop only if it's expected
|
||||
return React.cloneElement(child as React.ReactElement<any>, { onSelect });
|
||||
}
|
||||
return child; // Return non-element children as-is
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// Recursive Function to Render Nested Data
|
||||
const renderNestedData = (
|
||||
data: Record<string, any>,
|
||||
onSelect: (selectedLabel: string) => void
|
||||
) => {
|
||||
return Object.entries(data).map(([key, value]) => {
|
||||
if (typeof value === "object" && !Array.isArray(value)) {
|
||||
// If the value is an object, render it as a nested dropdown
|
||||
return (
|
||||
<NestedDropdown key={key} label={key} onSelect={onSelect}>
|
||||
{renderNestedData(value, onSelect)}
|
||||
</NestedDropdown>
|
||||
);
|
||||
} else if (Array.isArray(value)) {
|
||||
// If the value is an array, render each item as a dropdown item
|
||||
return value.map((item, index) => (
|
||||
<DropdownItem key={index} label={item} onClick={() => onSelect(item)} />
|
||||
));
|
||||
} else {
|
||||
// If the value is a simple string, render it as a dropdown item
|
||||
return (
|
||||
<DropdownItem key={key} label={value} onClick={() => onSelect(value)} />
|
||||
);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// Main Multi-Level Dropdown Component
|
||||
const MultiLevelDropdown = ({ data }: { data: Record<string, any> }) => {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [selectedLabel, setSelectedLabel] = useState("Dropdown trigger");
|
||||
const dropdownRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
// Handle outer click to close the dropdown
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
if (
|
||||
dropdownRef.current &&
|
||||
!dropdownRef.current.contains(event.target as Node)
|
||||
) {
|
||||
setOpen(false);
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener("mousedown", handleClickOutside);
|
||||
return () => {
|
||||
document.removeEventListener("mousedown", handleClickOutside);
|
||||
};
|
||||
}, []);
|
||||
|
||||
// Handle selection of an item
|
||||
const handleSelect = (selectedLabel: string) => {
|
||||
setSelectedLabel(selectedLabel); // Update the dropdown trigger text
|
||||
setOpen(false); // Close the dropdown
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="multi-level-dropdown" ref={dropdownRef}>
|
||||
{/* Dropdown Trigger Button */}
|
||||
<button
|
||||
className={`dropdown-button ${open ? "open" : ""}`}
|
||||
onClick={() => setOpen(!open)} // Toggle main menu on click
|
||||
>
|
||||
{selectedLabel} <span className="icon">▾</span>
|
||||
</button>
|
||||
|
||||
{/* Dropdown Menu */}
|
||||
{open && (
|
||||
<div className="dropdown-menu">
|
||||
<div className="dropdown-content">
|
||||
{renderNestedData(data, handleSelect)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default MultiLevelDropdown;
|
||||
@@ -1,127 +1,127 @@
|
||||
import React, { useState, useEffect, useRef } from "react";
|
||||
|
||||
interface DropdownProps {
|
||||
header: string;
|
||||
options: string[];
|
||||
onSelect: (option: string) => void;
|
||||
search?: boolean;
|
||||
onClick?: () => void;
|
||||
onChange?: () => void;
|
||||
}
|
||||
|
||||
const RegularDropDown: React.FC<DropdownProps> = ({
|
||||
header,
|
||||
options,
|
||||
onSelect,
|
||||
search = true,
|
||||
onClick,
|
||||
onChange,
|
||||
}) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [selectedOption, setSelectedOption] = useState<string | null>(null);
|
||||
const [searchTerm, setSearchTerm] = useState(""); // State to store search term
|
||||
const [filteredOptions, setFilteredOptions] = useState<string[]>(options); // State for filtered options
|
||||
const dropdownRef = useRef<HTMLDivElement>(null); // Ref for the dropdown container
|
||||
|
||||
// Reset selectedOption when the dropdown closes
|
||||
useEffect(() => {
|
||||
if (!isOpen) {
|
||||
setSelectedOption(null);
|
||||
setSearchTerm(""); // Clear the search term when the dropdown closes
|
||||
setFilteredOptions(options); // Reset filtered options when the dropdown closes
|
||||
}
|
||||
}, [isOpen, options]);
|
||||
|
||||
// Reset selectedOption when the header prop changes
|
||||
useEffect(() => {
|
||||
setSelectedOption(null);
|
||||
setSearchTerm(""); // Reset search term if header changes
|
||||
setFilteredOptions(options); // Reset options if header changes
|
||||
}, [header, options]);
|
||||
|
||||
// Close dropdown if clicked outside
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
if (
|
||||
dropdownRef.current &&
|
||||
!dropdownRef.current.contains(event.target as Node)
|
||||
) {
|
||||
setIsOpen(false);
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener("click", handleClickOutside);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener("click", handleClickOutside);
|
||||
};
|
||||
}, []);
|
||||
|
||||
// Toggle the dropdown
|
||||
const toggleDropdown = () => {
|
||||
setIsOpen((prev) => !prev);
|
||||
};
|
||||
|
||||
// Handle option selection
|
||||
const handleOptionClick = (option: string) => {
|
||||
setSelectedOption(option);
|
||||
onSelect(option);
|
||||
setIsOpen(false);
|
||||
};
|
||||
|
||||
// Handle search input change
|
||||
const handleSearchChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const term = event.target.value;
|
||||
setSearchTerm(term);
|
||||
|
||||
// Filter options based on the search term
|
||||
const filtered = options.filter((option) =>
|
||||
option.toLowerCase().includes(term.toLowerCase())
|
||||
);
|
||||
setFilteredOptions(filtered);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="regularDropdown-container" ref={dropdownRef}>
|
||||
{/* Dropdown Header */}
|
||||
<div className="dropdown-header flex-sb" onClick={toggleDropdown}>
|
||||
<div className="key">{selectedOption || header}</div>
|
||||
<div className="icon">▾</div>
|
||||
</div>
|
||||
|
||||
{/* Dropdown Options */}
|
||||
{isOpen && (
|
||||
<div className="dropdown-options">
|
||||
{/* Search Bar */}
|
||||
{search && (
|
||||
<div className="dropdown-search">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search..."
|
||||
value={searchTerm}
|
||||
onChange={handleSearchChange}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Filtered Options */}
|
||||
{filteredOptions.length > 0 ? (
|
||||
filteredOptions.map((option, index) => (
|
||||
<div
|
||||
className="option"
|
||||
key={index}
|
||||
onClick={() => handleOptionClick(option)}
|
||||
>
|
||||
{option}
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<div className="no-options">No options found</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default RegularDropDown;
|
||||
import React, { useState, useEffect, useRef } from "react";
|
||||
|
||||
interface DropdownProps {
|
||||
header: string;
|
||||
options: string[];
|
||||
onSelect: (option: string) => void;
|
||||
search?: boolean;
|
||||
onClick?: () => void;
|
||||
onChange?: () => void;
|
||||
}
|
||||
|
||||
const RegularDropDown: React.FC<DropdownProps> = ({
|
||||
header,
|
||||
options,
|
||||
onSelect,
|
||||
search = true,
|
||||
onClick,
|
||||
onChange,
|
||||
}) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [selectedOption, setSelectedOption] = useState<string | null>(null);
|
||||
const [searchTerm, setSearchTerm] = useState(""); // State to store search term
|
||||
const [filteredOptions, setFilteredOptions] = useState<string[]>(options); // State for filtered options
|
||||
const dropdownRef = useRef<HTMLDivElement>(null); // Ref for the dropdown container
|
||||
|
||||
// Reset selectedOption when the dropdown closes
|
||||
useEffect(() => {
|
||||
if (!isOpen) {
|
||||
setSelectedOption(null);
|
||||
setSearchTerm(""); // Clear the search term when the dropdown closes
|
||||
setFilteredOptions(options); // Reset filtered options when the dropdown closes
|
||||
}
|
||||
}, [isOpen, options]);
|
||||
|
||||
// Reset selectedOption when the header prop changes
|
||||
useEffect(() => {
|
||||
setSelectedOption(null);
|
||||
setSearchTerm(""); // Reset search term if header changes
|
||||
setFilteredOptions(options); // Reset options if header changes
|
||||
}, [header, options]);
|
||||
|
||||
// Close dropdown if clicked outside
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
if (
|
||||
dropdownRef.current &&
|
||||
!dropdownRef.current.contains(event.target as Node)
|
||||
) {
|
||||
setIsOpen(false);
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener("click", handleClickOutside);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener("click", handleClickOutside);
|
||||
};
|
||||
}, []);
|
||||
|
||||
// Toggle the dropdown
|
||||
const toggleDropdown = () => {
|
||||
setIsOpen((prev) => !prev);
|
||||
};
|
||||
|
||||
// Handle option selection
|
||||
const handleOptionClick = (option: string) => {
|
||||
setSelectedOption(option);
|
||||
onSelect(option);
|
||||
setIsOpen(false);
|
||||
};
|
||||
|
||||
// Handle search input change
|
||||
const handleSearchChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const term = event.target.value;
|
||||
setSearchTerm(term);
|
||||
|
||||
// Filter options based on the search term
|
||||
const filtered = options.filter((option) =>
|
||||
option.toLowerCase().includes(term.toLowerCase())
|
||||
);
|
||||
setFilteredOptions(filtered);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="regularDropdown-container" ref={dropdownRef}>
|
||||
{/* Dropdown Header */}
|
||||
<div className="dropdown-header flex-sb" onClick={toggleDropdown}>
|
||||
<div className="key">{selectedOption || header}</div>
|
||||
<div className="icon">▾</div>
|
||||
</div>
|
||||
|
||||
{/* Dropdown Options */}
|
||||
{isOpen && (
|
||||
<div className="dropdown-options">
|
||||
{/* Search Bar */}
|
||||
{search && (
|
||||
<div className="dropdown-search">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search..."
|
||||
value={searchTerm}
|
||||
onChange={handleSearchChange}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Filtered Options */}
|
||||
{filteredOptions.length > 0 ? (
|
||||
filteredOptions.map((option, index) => (
|
||||
<div
|
||||
className="option"
|
||||
key={index}
|
||||
onClick={() => handleOptionClick(option)}
|
||||
>
|
||||
{option}
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<div className="no-options">No options found</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default RegularDropDown;
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import React, { useState } from "react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import List from "./List";
|
||||
import { AddIcon, ArrowIcon, FocusIcon } from "../../icons/ExportCommonIcons";
|
||||
import KebabMenuListMultiSelect from "./KebebMenuListMultiSelect";
|
||||
import { getZonesApi } from "../../../services/realTimeVisulization/zoneData/getZones";
|
||||
|
||||
interface DropDownListProps {
|
||||
value?: string; // Value to display in the DropDownList
|
||||
@@ -33,6 +34,19 @@ const DropDownList: React.FC<DropDownListProps> = ({
|
||||
const handleToggle = () => {
|
||||
setIsOpen((prev) => !prev); // Toggle the state
|
||||
};
|
||||
const [zoneDataList, setZoneDataList] = useState<{ id: string; name: string }[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
async function GetZoneData() {
|
||||
const response = await getZonesApi("hexrfactory")
|
||||
console.log('response: ', response.data);
|
||||
setZoneDataList([{ id: "1", name: "zone1" },
|
||||
{ id: "2", name: "Zone 2" },])
|
||||
}
|
||||
|
||||
GetZoneData()
|
||||
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className="dropdown-list-container">
|
||||
@@ -79,7 +93,7 @@ const DropDownList: React.FC<DropDownListProps> = ({
|
||||
value="Zones"
|
||||
showKebabMenu={false}
|
||||
showAddIcon={false}
|
||||
items={[]}
|
||||
items={zoneDataList}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -8,6 +8,7 @@ interface ListProps {
|
||||
}
|
||||
|
||||
const List: React.FC<ListProps> = ({ items = [] }) => {
|
||||
console.log('items: ', items);
|
||||
return (
|
||||
<>
|
||||
{items.length > 0 ? (
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { StockIncreseIcon } from "../../../icons/RealTimeVisulationIcons";
|
||||
|
||||
const ProgressCard = ({
|
||||
title,
|
||||
data,
|
||||
@@ -16,7 +18,9 @@ const ProgressCard = ({
|
||||
</span>
|
||||
<div className="stock-description">{stock.description}</div>
|
||||
</span>
|
||||
<div className="icon">Icon</div>
|
||||
<div className="icon">
|
||||
<StockIncreseIcon />
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
import React, { useMemo } from "react";
|
||||
import { Radar } from "react-chartjs-2";
|
||||
import {
|
||||
ChartOptions,
|
||||
ChartData,
|
||||
RadialLinearScaleOptions,
|
||||
} from "chart.js";
|
||||
import { ChartOptions, ChartData, RadialLinearScaleOptions } from "chart.js";
|
||||
|
||||
interface ChartComponentProps {
|
||||
type: string;
|
||||
title: string;
|
||||
fontFamily?: string;
|
||||
fontSize?: string;
|
||||
@@ -19,7 +16,7 @@ const RadarGraphComponent = ({
|
||||
fontFamily,
|
||||
fontSize,
|
||||
fontWeight = "Regular",
|
||||
data,
|
||||
data, // Now guaranteed to be number[]
|
||||
}: ChartComponentProps) => {
|
||||
// Memoize Font Weight Mapping
|
||||
const chartFontWeightMap = useMemo(
|
||||
@@ -31,14 +28,15 @@ const RadarGraphComponent = ({
|
||||
[]
|
||||
);
|
||||
|
||||
const fontSizeValue = useMemo(() => (fontSize ? parseInt(fontSize) : 12), [
|
||||
fontSize,
|
||||
]);
|
||||
const fontSizeValue = useMemo(
|
||||
() => (fontSize ? parseInt(fontSize) : 12),
|
||||
[fontSize]
|
||||
);
|
||||
|
||||
const fontWeightValue = useMemo(() => chartFontWeightMap[fontWeight], [
|
||||
fontWeight,
|
||||
chartFontWeightMap,
|
||||
]);
|
||||
const fontWeightValue = useMemo(
|
||||
() => chartFontWeightMap[fontWeight],
|
||||
[fontWeight, chartFontWeightMap]
|
||||
);
|
||||
|
||||
const chartFontStyle = useMemo(
|
||||
() => ({
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
export const handleResize = (
|
||||
e: React.MouseEvent<HTMLDivElement>,
|
||||
containerRef: React.RefObject<HTMLDivElement | null>
|
||||
) => {
|
||||
if (!containerRef.current) return; // Ensure containerRef is not null
|
||||
const startY = e.clientY;
|
||||
const startHeight = containerRef.current.offsetHeight;
|
||||
|
||||
const onMouseMove = (moveEvent: MouseEvent) => {
|
||||
const newHeight = Math.max(
|
||||
120,
|
||||
Math.min(400, startHeight + moveEvent.clientY - startY)
|
||||
);
|
||||
containerRef.current!.style.height = `${newHeight}px`;
|
||||
};
|
||||
|
||||
const onMouseUp = () => {
|
||||
document.removeEventListener("mousemove", onMouseMove);
|
||||
document.removeEventListener("mouseup", onMouseUp);
|
||||
};
|
||||
|
||||
document.addEventListener("mousemove", onMouseMove);
|
||||
document.addEventListener("mouseup", onMouseUp);
|
||||
};
|
||||
export const handleResize = (
|
||||
e: React.MouseEvent<HTMLDivElement>,
|
||||
containerRef: React.RefObject<HTMLDivElement | null>
|
||||
) => {
|
||||
if (!containerRef.current) return; // Ensure containerRef is not null
|
||||
const startY = e.clientY;
|
||||
const startHeight = containerRef.current.offsetHeight;
|
||||
|
||||
const onMouseMove = (moveEvent: MouseEvent) => {
|
||||
const newHeight = Math.max(
|
||||
120,
|
||||
Math.min(400, startHeight + moveEvent.clientY - startY)
|
||||
);
|
||||
containerRef.current!.style.height = `${newHeight}px`;
|
||||
};
|
||||
|
||||
const onMouseUp = () => {
|
||||
document.removeEventListener("mousemove", onMouseMove);
|
||||
document.removeEventListener("mouseup", onMouseUp);
|
||||
};
|
||||
|
||||
document.addEventListener("mousemove", onMouseMove);
|
||||
document.addEventListener("mouseup", onMouseUp);
|
||||
};
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
||||
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
||||
sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
||||
monospace;
|
||||
}
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
||||
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
||||
sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
||||
monospace;
|
||||
}
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import './index.css';
|
||||
import App from './app';
|
||||
import reportWebVitals from './reportWebVitals';
|
||||
|
||||
const root = ReactDOM.createRoot(
|
||||
document.getElementById('root') as HTMLElement
|
||||
);
|
||||
root.render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>
|
||||
);
|
||||
|
||||
// If you want to start measuring performance in your app, pass a function
|
||||
// to log results (for example: reportWebVitals(console.log))
|
||||
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
|
||||
reportWebVitals();
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import './index.css';
|
||||
import App from './app';
|
||||
import reportWebVitals from './reportWebVitals';
|
||||
|
||||
const root = ReactDOM.createRoot(
|
||||
document.getElementById('root') as HTMLElement
|
||||
);
|
||||
root.render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>
|
||||
);
|
||||
|
||||
// If you want to start measuring performance in your app, pass a function
|
||||
// to log results (for example: reportWebVitals(console.log))
|
||||
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
|
||||
reportWebVitals();
|
||||
|
||||
54
app/src/modules/builder/csg/csg.tsx
Normal file
@@ -0,0 +1,54 @@
|
||||
import * as THREE from "three";
|
||||
import { Geometry, Base, Subtraction } from "@react-three/csg";
|
||||
import { useDeleteModels } from "../../../store/store";
|
||||
import { useRef } from "react";
|
||||
|
||||
export interface CsgProps {
|
||||
position: THREE.Vector3 | [number, number, number];
|
||||
scale: THREE.Vector3 | [number, number, number];
|
||||
model: THREE.Object3D;
|
||||
hoveredDeletableWallItem: { current: THREE.Mesh | null };
|
||||
}
|
||||
|
||||
export const Csg: React.FC<CsgProps> = (props) => {
|
||||
const { deleteModels } = useDeleteModels();
|
||||
const modelRef = useRef<THREE.Object3D>();
|
||||
const originalMaterials = useRef<Map<THREE.Mesh, THREE.Material>>(new Map());
|
||||
|
||||
const handleHover = (hovered: boolean, object: THREE.Mesh | null) => {
|
||||
if (modelRef.current && deleteModels) {
|
||||
modelRef.current.traverse((child) => {
|
||||
if (child instanceof THREE.Mesh) {
|
||||
if (!originalMaterials.current.has(child)) {
|
||||
originalMaterials.current.set(child, child.material);
|
||||
}
|
||||
child.material = child.material.clone();
|
||||
child.material.color.set(hovered && deleteModels ? 0xff0000 : (originalMaterials.current.get(child) as any).color);
|
||||
}
|
||||
});
|
||||
}
|
||||
props.hoveredDeletableWallItem.current = hovered ? object : null;
|
||||
};
|
||||
|
||||
return (
|
||||
<Geometry>
|
||||
<Subtraction {...props}>
|
||||
<Geometry>
|
||||
<Base geometry={new THREE.BoxGeometry()} />
|
||||
</Geometry>
|
||||
</Subtraction>
|
||||
<primitive
|
||||
object={props.model}
|
||||
ref={modelRef}
|
||||
onPointerOver={(e: any) => {
|
||||
e.stopPropagation();
|
||||
handleHover(true, e.object.parent);
|
||||
}}
|
||||
onPointerOut={(e: any) => {
|
||||
e.stopPropagation();
|
||||
handleHover(false, null);
|
||||
}}
|
||||
/>
|
||||
</Geometry>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,80 @@
|
||||
import * as THREE from 'three';
|
||||
import { DragControls } from 'three/examples/jsm/controls/DragControls';
|
||||
import * as CONSTANTS from '../../../types/world/worldConstants';
|
||||
import DragPoint from '../geomentries/points/dragPoint';
|
||||
|
||||
import * as Types from "../../../types/world/worldTypes";
|
||||
// import { updatePoint } from '../../../services/factoryBuilder/lines/updatePointApi';
|
||||
import { Socket } from 'socket.io-client';
|
||||
|
||||
export default async function addDragControl(
|
||||
dragPointControls: Types.RefDragControl,
|
||||
currentLayerPoint: Types.RefMeshArray,
|
||||
state: Types.ThreeState,
|
||||
floorPlanGroupPoint: Types.RefGroup,
|
||||
floorPlanGroupLine: Types.RefGroup,
|
||||
lines: Types.RefLines,
|
||||
onlyFloorlines: Types.RefOnlyFloorLines,
|
||||
socket: Socket<any>
|
||||
) {
|
||||
|
||||
////////// Dragging Point and also change the size to indicate during hover //////////
|
||||
|
||||
dragPointControls.current = new DragControls(currentLayerPoint.current, state.camera, state.gl.domElement);
|
||||
dragPointControls.current.enabled = false;
|
||||
|
||||
dragPointControls.current.addEventListener('drag', function (event) {
|
||||
const object = event.object;
|
||||
if (object.visible) {
|
||||
(state.controls as any).enabled = false;
|
||||
DragPoint(event as any, floorPlanGroupPoint, floorPlanGroupLine, state.scene, lines, onlyFloorlines)
|
||||
} else {
|
||||
(state.controls as any).enabled = true;
|
||||
}
|
||||
});
|
||||
|
||||
dragPointControls.current.addEventListener('dragstart', function (event) {
|
||||
});
|
||||
|
||||
dragPointControls.current.addEventListener('dragend', async function (event) {
|
||||
if (!dragPointControls.current) return;
|
||||
const email = localStorage.getItem('email')
|
||||
const organization = (email!.split("@")[1]).split(".")[0];
|
||||
|
||||
//REST
|
||||
|
||||
// await updatePoint(
|
||||
// organization,
|
||||
// { "x": event.object.position.x, "y": 0.01, "z": event.object.position.z },
|
||||
// event.object.uuid,
|
||||
// )
|
||||
|
||||
//SOCKET
|
||||
|
||||
const data = {
|
||||
organization: organization,
|
||||
position: { "x": event.object.position.x, "y": 0.01, "z": event.object.position.z },
|
||||
uuid: event.object.uuid,
|
||||
socketId: socket.id
|
||||
}
|
||||
|
||||
socket.emit('v1:Line:update', data);
|
||||
|
||||
if (state.controls) {
|
||||
(state.controls as any).enabled = true;
|
||||
}
|
||||
});
|
||||
|
||||
dragPointControls.current.addEventListener('hoveron', function (event: any) {
|
||||
if ((event.object as Types.Mesh).name === "point") {
|
||||
event.object.material.uniforms.uInnerColor.value.set(event.object.userData.color)
|
||||
}
|
||||
});
|
||||
|
||||
dragPointControls.current.addEventListener('hoveroff', function (event: any) {
|
||||
if ((event.object as Types.Mesh).name === "point") {
|
||||
event.object.material.uniforms.uInnerColor.value.set(new THREE.Color(CONSTANTS.pointConfig.defaultInnerColor))
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import * as Types from "../../../types/world/worldTypes";
|
||||
|
||||
export default function handleContextMenu(
|
||||
menuVisible: Types.Boolean,
|
||||
setMenuVisible: Types.BooleanState
|
||||
): void {
|
||||
// setMenuVisible(true)
|
||||
}
|
||||
64
app/src/modules/builder/eventFunctions/handleMeshDown.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import * as THREE from 'three';
|
||||
|
||||
import * as Types from "../../../types/world/worldTypes";
|
||||
|
||||
function handleMeshDown(
|
||||
event: Types.MeshEvent,
|
||||
currentWallItem: Types.RefMesh,
|
||||
setSelectedWallItem: Types.setSelectedWallItemSetState,
|
||||
setSelectedItemsIndex: Types.setSelectedItemsIndexSetState,
|
||||
wallItems: Types.wallItems,
|
||||
toggleView: Types.Boolean
|
||||
): void {
|
||||
|
||||
////////// To select which of the Wall item and CSG is selected to be dragged //////////
|
||||
|
||||
if (!toggleView) {
|
||||
if (currentWallItem.current) {
|
||||
currentWallItem.current.children.forEach((child) => {
|
||||
if ((child as THREE.Mesh).isMesh && child.name !== "CSG_REF") {
|
||||
const material = (child as THREE.Mesh).material;
|
||||
if (Array.isArray(material)) {
|
||||
material.forEach(mat => {
|
||||
if (mat instanceof THREE.MeshStandardMaterial) {
|
||||
mat.emissive = new THREE.Color("black");
|
||||
}
|
||||
});
|
||||
} else if (material instanceof THREE.MeshStandardMaterial) {
|
||||
material.emissive = new THREE.Color("black");
|
||||
}
|
||||
}
|
||||
});
|
||||
currentWallItem.current = null;
|
||||
setSelectedWallItem(null);
|
||||
setSelectedItemsIndex(null);
|
||||
}
|
||||
|
||||
if (event.intersections.length > 0) {
|
||||
const clickedIndex = wallItems.findIndex((item) => item.model === event.intersections[0]?.object?.parent?.parent);
|
||||
if (clickedIndex !== -1) {
|
||||
setSelectedItemsIndex(clickedIndex);
|
||||
const wallItemModel = wallItems[clickedIndex]?.model;
|
||||
if (wallItemModel && wallItemModel.parent && wallItemModel.parent.parent) {
|
||||
currentWallItem.current = (wallItemModel.parent.parent.children[0]?.children[1]?.children[0] as Types.Mesh) || null;
|
||||
setSelectedWallItem(wallItemModel.parent);
|
||||
// currentWallItem.current?.children.forEach((child) => {
|
||||
// if ((child as THREE.Mesh).isMesh && child.name !== "CSG_REF") {
|
||||
// const material = (child as THREE.Mesh).material;
|
||||
// if (Array.isArray(material)) {
|
||||
// material.forEach(mat => {
|
||||
// if (mat instanceof THREE.MeshStandardMaterial) {
|
||||
// mat.emissive = new THREE.Color("green");
|
||||
// }
|
||||
// });
|
||||
// } else if (material instanceof THREE.MeshStandardMaterial) {
|
||||
// material.emissive = new THREE.Color("green");
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
export default handleMeshDown;
|
||||
34
app/src/modules/builder/eventFunctions/handleMeshMissed.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import * as THREE from 'three';
|
||||
import * as Types from "../../../types/world/worldTypes";
|
||||
|
||||
function handleMeshMissed(
|
||||
currentWallItem: Types.RefMesh,
|
||||
setSelectedWallItem: Types.setSelectedWallItemSetState,
|
||||
setSelectedItemsIndex: Types.setSelectedItemsIndexSetState
|
||||
): void {
|
||||
|
||||
////////// If an item is selected and then clicked outside other than the selected object, this runs and removes the color of the selected object and sets setSelectedWallItem and setSelectedItemsIndex as null //////////
|
||||
|
||||
if (currentWallItem.current) {
|
||||
currentWallItem.current.children.forEach((child) => {
|
||||
if ((child as THREE.Mesh).isMesh && child.name !== "CSG_REF") {
|
||||
const material = (child as THREE.Mesh).material;
|
||||
|
||||
if (Array.isArray(material)) {
|
||||
material.forEach(mat => {
|
||||
if (mat instanceof THREE.MeshStandardMaterial) {
|
||||
mat.emissive = new THREE.Color("black");
|
||||
}
|
||||
});
|
||||
} else if (material instanceof THREE.MeshStandardMaterial) {
|
||||
material.emissive = new THREE.Color("black");
|
||||
}
|
||||
}
|
||||
});
|
||||
currentWallItem.current = null;
|
||||
setSelectedWallItem(null);
|
||||
setSelectedItemsIndex(null);
|
||||
}
|
||||
}
|
||||
|
||||
export default handleMeshMissed;
|
||||
87
app/src/modules/builder/functions/deletableLineOrPoint.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
import * as THREE from 'three';
|
||||
import * as CONSTANTS from '../../../types/world/worldConstants';
|
||||
import * as Types from "../../../types/world/worldTypes";
|
||||
|
||||
function DeletableLineorPoint(
|
||||
state: Types.ThreeState,
|
||||
plane: Types.RefMesh,
|
||||
floorPlanGroupLine: Types.RefGroup,
|
||||
floorPlanGroupPoint: Types.RefGroup,
|
||||
hoveredDeletableLine: Types.RefMesh,
|
||||
hoveredDeletablePoint: Types.RefMesh
|
||||
): void {
|
||||
|
||||
////////// Altering the color of the hovered line or point during the deletion time //////////
|
||||
|
||||
if (!plane.current) return;
|
||||
let intersects = state.raycaster.intersectObject(plane.current, true);
|
||||
|
||||
let visibleIntersectLines;
|
||||
if (floorPlanGroupLine.current) { visibleIntersectLines = state.raycaster?.intersectObjects(floorPlanGroupLine.current.children, true); }
|
||||
const visibleIntersectLine = visibleIntersectLines?.find(intersect => intersect.object.visible) as THREE.Line | undefined || null;
|
||||
|
||||
let visibleIntersectPoints;
|
||||
if (floorPlanGroupPoint.current) {
|
||||
visibleIntersectPoints = state.raycaster?.intersectObjects(floorPlanGroupPoint.current.children, true);
|
||||
}
|
||||
const visibleIntersectPoint = visibleIntersectPoints?.find(intersect => intersect.object.visible) as THREE.Mesh | undefined;
|
||||
|
||||
function getLineColor(lineType: string | undefined): string {
|
||||
switch (lineType) {
|
||||
case CONSTANTS.lineConfig.wallName: return CONSTANTS.lineConfig.wallColor;
|
||||
case CONSTANTS.lineConfig.floorName: return CONSTANTS.lineConfig.floorColor;
|
||||
case CONSTANTS.lineConfig.aisleName: return CONSTANTS.lineConfig.aisleColor;
|
||||
default: return CONSTANTS.lineConfig.defaultColor;
|
||||
}
|
||||
}
|
||||
|
||||
if (intersects.length > 0) {
|
||||
if (visibleIntersectPoint) {
|
||||
if (hoveredDeletableLine.current) {
|
||||
const lineType = hoveredDeletableLine.current.userData.linePoints[1]?.[3];
|
||||
const color = getLineColor(lineType);
|
||||
(hoveredDeletableLine.current.material as THREE.MeshBasicMaterial).color = new THREE.Color(color);
|
||||
hoveredDeletableLine.current = null;
|
||||
}
|
||||
|
||||
hoveredDeletablePoint.current = (visibleIntersectPoint as any).object;
|
||||
(hoveredDeletablePoint.current as any).material.uniforms.uInnerColor.value.set(new THREE.Color("red"));
|
||||
(hoveredDeletablePoint.current as any).material.uniforms.uColor.value.set(new THREE.Color("red"));
|
||||
// (hoveredDeletablePoint.current as THREE.Mesh).scale.set(1.5, 1.5, 1.5);
|
||||
} else if (hoveredDeletablePoint.current) {
|
||||
(hoveredDeletablePoint.current as any).material.uniforms.uInnerColor.value.set(CONSTANTS.pointConfig.defaultInnerColor);
|
||||
(hoveredDeletablePoint.current as any).material.uniforms.uColor.value.set((hoveredDeletablePoint.current as any).userData.color);
|
||||
// hoveredDeletablePoint.current.scale.set(1, 1, 1);
|
||||
hoveredDeletablePoint.current = null;
|
||||
}
|
||||
|
||||
if (visibleIntersectLine && !visibleIntersectPoint) {
|
||||
if (hoveredDeletableLine.current) {
|
||||
const lineType = hoveredDeletableLine.current.userData.linePoints[1]?.[3];
|
||||
const color = getLineColor(lineType);
|
||||
(hoveredDeletableLine.current.material as THREE.MeshBasicMaterial).color = new THREE.Color(color);
|
||||
hoveredDeletableLine.current = null;
|
||||
}
|
||||
|
||||
if (hoveredDeletablePoint.current) {
|
||||
(hoveredDeletablePoint.current as any).material.uniforms.uInnerColor.value.set(CONSTANTS.pointConfig.defaultInnerColor);
|
||||
(hoveredDeletablePoint.current as any).material.uniforms.uColor.value.set((hoveredDeletablePoint.current as any).userData.color);
|
||||
// hoveredDeletablePoint.current.scale.set(1, 1, 1);
|
||||
hoveredDeletablePoint.current = null;
|
||||
}
|
||||
|
||||
hoveredDeletableLine.current = (visibleIntersectLine as any).object;
|
||||
if (hoveredDeletableLine.current) {
|
||||
(hoveredDeletableLine.current.material as THREE.MeshBasicMaterial).color = new THREE.Color("red");
|
||||
}
|
||||
} else if (hoveredDeletableLine.current) {
|
||||
const lineType = hoveredDeletableLine.current.userData.linePoints[1]?.[3];
|
||||
const color = getLineColor(lineType);
|
||||
(hoveredDeletableLine.current.material as THREE.MeshBasicMaterial).color = new THREE.Color(color);
|
||||
hoveredDeletableLine.current = null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default DeletableLineorPoint;
|
||||
97
app/src/modules/builder/functions/draw.ts
Normal file
@@ -0,0 +1,97 @@
|
||||
import * as Types from "../../../types/world/worldTypes";
|
||||
import * as CONSTANTS from '../../../types/world/worldConstants';
|
||||
import createAndMoveReferenceLine from "../geomentries/lines/createAndMoveReferenceLine";
|
||||
|
||||
async function Draw(
|
||||
state: Types.ThreeState,
|
||||
plane: Types.RefMesh,
|
||||
cursorPosition: Types.Vector3,
|
||||
floorPlanGroupPoint: Types.RefGroup,
|
||||
floorPlanGroupLine: Types.RefGroup,
|
||||
snappedPoint: Types.RefVector3,
|
||||
isSnapped: Types.RefBoolean,
|
||||
isSnappedUUID: Types.RefString,
|
||||
line: Types.RefLine,
|
||||
lines: Types.RefLines,
|
||||
ispreSnapped: Types.RefBoolean,
|
||||
floorPlanGroup: Types.RefGroup,
|
||||
ReferenceLineMesh: Types.RefMesh,
|
||||
LineCreated: Types.RefBoolean,
|
||||
setRefTextUpdate: Types.NumberIncrementState,
|
||||
Tube: Types.RefTubeGeometry,
|
||||
anglesnappedPoint: Types.RefVector3,
|
||||
isAngleSnapped: Types.RefBoolean,
|
||||
toolMode: Types.String,
|
||||
): Promise<void> {
|
||||
|
||||
////////// Snapping the cursor during the drawing time and also changing the color of the intersected lines //////////
|
||||
|
||||
if (!plane.current) return;
|
||||
const intersects = state.raycaster.intersectObject(plane.current, true);
|
||||
|
||||
if (intersects.length > 0 && (toolMode === "Wall" || toolMode === "Aisle" || toolMode === "Floor")) {
|
||||
const intersectionPoint = intersects[0].point;
|
||||
cursorPosition.copy(intersectionPoint);
|
||||
const snapThreshold = 1;
|
||||
|
||||
if (line.current.length === 0) {
|
||||
for (const point of floorPlanGroupPoint.current.children) {
|
||||
const pointType = point.userData.type;
|
||||
|
||||
const canSnap =
|
||||
((toolMode === "Wall") && (pointType === CONSTANTS.lineConfig.wallName || pointType === CONSTANTS.lineConfig.floorName)) ||
|
||||
((toolMode === "Floor") && (pointType === CONSTANTS.lineConfig.wallName || pointType === CONSTANTS.lineConfig.floorName)) ||
|
||||
((toolMode === "Aisle") && pointType === CONSTANTS.lineConfig.aisleName);;
|
||||
|
||||
if (canSnap && cursorPosition.distanceTo(point.position) < snapThreshold + 0.5 && point.visible) {
|
||||
cursorPosition.copy(point.position);
|
||||
snappedPoint.current = point.position;
|
||||
ispreSnapped.current = true;
|
||||
isSnapped.current = false;
|
||||
isSnappedUUID.current = point.uuid;
|
||||
break;
|
||||
} else {
|
||||
ispreSnapped.current = false;
|
||||
}
|
||||
}
|
||||
} else if (line.current.length > 0 && line.current[0]) {
|
||||
for (const point of floorPlanGroupPoint.current.children) {
|
||||
const pointType = point.userData.type;
|
||||
|
||||
let canSnap =
|
||||
((toolMode === "Wall") && (pointType === CONSTANTS.lineConfig.wallName || pointType === CONSTANTS.lineConfig.floorName)) ||
|
||||
((toolMode === "Floor") && (pointType === CONSTANTS.lineConfig.wallName || pointType === CONSTANTS.lineConfig.floorName)) ||
|
||||
((toolMode === "Aisle") && pointType === CONSTANTS.lineConfig.aisleName);
|
||||
|
||||
if (canSnap && cursorPosition.distanceTo(point.position) < snapThreshold && point.visible) {
|
||||
cursorPosition.copy(point.position);
|
||||
snappedPoint.current = point.position;
|
||||
isSnapped.current = true;
|
||||
ispreSnapped.current = false;
|
||||
isSnappedUUID.current = point.uuid;
|
||||
break;
|
||||
} else {
|
||||
isSnapped.current = false;
|
||||
}
|
||||
}
|
||||
|
||||
createAndMoveReferenceLine(
|
||||
line.current[0][0],
|
||||
cursorPosition,
|
||||
isSnapped,
|
||||
ispreSnapped,
|
||||
line,
|
||||
setRefTextUpdate,
|
||||
floorPlanGroup,
|
||||
ReferenceLineMesh,
|
||||
LineCreated,
|
||||
Tube,
|
||||
anglesnappedPoint,
|
||||
isAngleSnapped
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default Draw;
|
||||
@@ -0,0 +1,56 @@
|
||||
import * as THREE from 'three';
|
||||
import * as Types from '../../../../types/world/worldTypes';
|
||||
import * as CONSTANTS from '../../../../types/world/worldConstants';
|
||||
|
||||
export default async function addAisleToScene(
|
||||
aisle: Types.Line,
|
||||
floorGroupAisle: Types.RefGroup,
|
||||
): Promise<void> {
|
||||
if (aisle.length >= 2 && aisle[0] && aisle[1]) {
|
||||
const start: Types.Vector3 = aisle[0][0];
|
||||
const end: Types.Vector3 = aisle[1][0];
|
||||
|
||||
const direction = new THREE.Vector3(
|
||||
end.x - start.x,
|
||||
end.y - start.y,
|
||||
end.z - start.z
|
||||
).normalize();
|
||||
|
||||
const perp = new THREE.Vector3(-direction.z, 0, direction.x).normalize();
|
||||
const offsetDistance = CONSTANTS.aisleConfig.width;
|
||||
|
||||
const leftStart = new THREE.Vector3().copy(start).addScaledVector(perp, offsetDistance);
|
||||
const rightStart = new THREE.Vector3().copy(start).addScaledVector(perp, -offsetDistance);
|
||||
const leftEnd = new THREE.Vector3().copy(end).addScaledVector(perp, offsetDistance);
|
||||
const rightEnd = new THREE.Vector3().copy(end).addScaledVector(perp, -offsetDistance);
|
||||
|
||||
const stripShape = new THREE.Shape();
|
||||
stripShape.moveTo(leftStart.x, leftStart.z);
|
||||
stripShape.lineTo(leftEnd.x, leftEnd.z);
|
||||
stripShape.lineTo(rightEnd.x, rightEnd.z);
|
||||
stripShape.lineTo(rightStart.x, rightStart.z);
|
||||
stripShape.lineTo(leftStart.x, leftStart.z);
|
||||
|
||||
const extrudeSettings = {
|
||||
depth: CONSTANTS.aisleConfig.height,
|
||||
bevelEnabled: false,
|
||||
};
|
||||
|
||||
const stripGeometry = new THREE.ExtrudeGeometry(stripShape, extrudeSettings);
|
||||
const stripMaterial = new THREE.MeshStandardMaterial({
|
||||
color: CONSTANTS.aisleConfig.defaultColor,
|
||||
polygonOffset: true,
|
||||
polygonOffsetFactor: -1,
|
||||
polygonOffsetUnits: -1,
|
||||
});
|
||||
|
||||
const stripMesh = new THREE.Mesh(stripGeometry, stripMaterial);
|
||||
stripMesh.receiveShadow = true;
|
||||
stripMesh.castShadow = true;
|
||||
|
||||
stripMesh.position.y = (aisle[0][2] - 1) * CONSTANTS.wallConfig.height + 0.01;
|
||||
stripMesh.rotateX(Math.PI / 2);
|
||||
|
||||
floorGroupAisle.current.add(stripMesh);
|
||||
}
|
||||
}
|
||||
19
app/src/modules/builder/geomentries/aisles/loadAisles.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import * as Types from '../../../../types/world/worldTypes';
|
||||
import addAisleToScene from './addAilseToScene';
|
||||
import * as CONSTANTS from '../../../../types/world/worldConstants';
|
||||
|
||||
export default async function loadAisles(
|
||||
lines: Types.RefLines,
|
||||
floorGroupAisle: Types.RefGroup
|
||||
) {
|
||||
// console.log('lines: ', lines.current[0][0][0]);
|
||||
if (!floorGroupAisle.current) return
|
||||
floorGroupAisle.current.children = [];
|
||||
const aisles = lines.current.filter((line) => line[0][3] && line[1][3] === CONSTANTS.lineConfig.aisleName);
|
||||
|
||||
if (aisles.length > 0) {
|
||||
aisles.forEach((aisle: Types.Line) => {
|
||||
addAisleToScene(aisle, floorGroupAisle)
|
||||
})
|
||||
}
|
||||
}
|
||||
186
app/src/modules/builder/geomentries/assets/addAssetModel.ts
Normal file
@@ -0,0 +1,186 @@
|
||||
import * as THREE from 'three';
|
||||
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
|
||||
import gsap from 'gsap';
|
||||
import { toast } from 'react-toastify';
|
||||
import TempLoader from './tempLoader';
|
||||
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader';
|
||||
import * as Types from "../../../../types/world/worldTypes";
|
||||
import { retrieveGLTF, storeGLTF } from '../../../../utils/indexDB/idbUtils';
|
||||
// import { setFloorItemApi } from '../../../../services/factoryBuilder/assest/floorAsset/setFloorItemApi';
|
||||
import { Socket } from 'socket.io-client';
|
||||
import * as CONSTANTS from '../../../../types/world/worldConstants';
|
||||
|
||||
async function addAssetModel(
|
||||
raycaster: THREE.Raycaster,
|
||||
camera: THREE.Camera,
|
||||
pointer: THREE.Vector2,
|
||||
floorGroup: Types.RefGroup,
|
||||
setFloorItems: Types.setFloorItemSetState,
|
||||
itemsGroup: Types.RefGroup,
|
||||
isTempLoader: Types.RefBoolean,
|
||||
tempLoader: Types.RefMesh,
|
||||
socket: Socket<any>,
|
||||
selectedItem: any,
|
||||
setSelectedItem: any,
|
||||
plane: Types.RefMesh,
|
||||
): Promise<void> {
|
||||
|
||||
////////// Load Floor GLtf's and set the positions, rotation, type etc. in state and store in localstorage //////////
|
||||
|
||||
let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_MARKETPLACE_URL}`;
|
||||
|
||||
try {
|
||||
isTempLoader.current = true;
|
||||
const loader = new GLTFLoader();
|
||||
const dracoLoader = new DRACOLoader();
|
||||
|
||||
dracoLoader.setDecoderPath('https://cdn.jsdelivr.net/npm/three@0.160.0/examples/jsm/libs/draco/gltf/');
|
||||
loader.setDRACOLoader(dracoLoader);
|
||||
|
||||
raycaster.setFromCamera(pointer, camera);
|
||||
const floorIntersections = raycaster.intersectObjects(floorGroup.current.children, true);
|
||||
const intersectedFloor = floorIntersections.find(intersect => intersect.object.name.includes("Floor"));
|
||||
|
||||
const planeIntersections = raycaster.intersectObject(plane.current!, true);
|
||||
const intersectedPlane = planeIntersections[0];
|
||||
|
||||
let intersectPoint: THREE.Vector3 | null = null;
|
||||
|
||||
if (intersectedFloor && intersectedPlane) {
|
||||
intersectPoint = intersectedFloor.distance < intersectedPlane.distance ? (new THREE.Vector3(intersectedFloor.point.x, Math.round(intersectedFloor.point.y), intersectedFloor.point.z)) : (new THREE.Vector3(intersectedPlane.point.x, 0, intersectedPlane.point.z));
|
||||
} else if (intersectedFloor) {
|
||||
intersectPoint = new THREE.Vector3(intersectedFloor.point.x, Math.round(intersectedFloor.point.y), intersectedFloor.point.z);
|
||||
} else if (intersectedPlane) {
|
||||
intersectPoint = new THREE.Vector3(intersectedPlane.point.x, 0, intersectedPlane.point.z);
|
||||
}
|
||||
|
||||
if (intersectPoint) {
|
||||
if (intersectPoint.y < 0) {
|
||||
intersectPoint = new THREE.Vector3(intersectPoint.x, 0, intersectPoint.z);
|
||||
}
|
||||
const cachedModel = THREE.Cache.get(selectedItem.id);
|
||||
if (cachedModel) {
|
||||
// console.log(`[Cache] Fetching ${selectedItem.name}`);
|
||||
handleModelLoad(cachedModel, intersectPoint!, selectedItem, itemsGroup, tempLoader, isTempLoader, setFloorItems, socket);
|
||||
return;
|
||||
} else {
|
||||
const cachedModelBlob = await retrieveGLTF(selectedItem.id);
|
||||
|
||||
if (cachedModelBlob) {
|
||||
// console.log(`Added ${selectedItem.name} from indexDB`);
|
||||
|
||||
const blobUrl = URL.createObjectURL(cachedModelBlob);
|
||||
loader.load(blobUrl, (gltf) => {
|
||||
URL.revokeObjectURL(blobUrl);
|
||||
THREE.Cache.remove(blobUrl);
|
||||
THREE.Cache.add(selectedItem.id, gltf);
|
||||
handleModelLoad(gltf, intersectPoint!, selectedItem, itemsGroup, tempLoader, isTempLoader, setFloorItems, socket);
|
||||
},
|
||||
() => {
|
||||
TempLoader(intersectPoint!, isTempLoader, tempLoader, itemsGroup);
|
||||
});
|
||||
} else {
|
||||
// console.log(`Added ${selectedItem.name} from Backend`);
|
||||
|
||||
loader.load(`${url_Backend_dwinzo}/api/v1/AssetFile/${selectedItem.id}`, async (gltf) => {
|
||||
const modelBlob = await fetch(`${url_Backend_dwinzo}/api/v1/AssetFile/${selectedItem.id}`).then((res) => res.blob());
|
||||
await storeGLTF(selectedItem.id, modelBlob);
|
||||
THREE.Cache.add(selectedItem.id, gltf);
|
||||
await handleModelLoad(gltf, intersectPoint!, selectedItem, itemsGroup, tempLoader, isTempLoader, setFloorItems, socket);
|
||||
},
|
||||
() => {
|
||||
TempLoader(intersectPoint!, isTempLoader, tempLoader, itemsGroup);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching asset model:', error);
|
||||
} finally {
|
||||
setSelectedItem({});
|
||||
}
|
||||
}
|
||||
|
||||
async function handleModelLoad(
|
||||
gltf: any,
|
||||
intersectPoint: THREE.Vector3,
|
||||
selectedItem: any,
|
||||
itemsGroup: Types.RefGroup,
|
||||
tempLoader: Types.RefMesh,
|
||||
isTempLoader: Types.RefBoolean,
|
||||
setFloorItems: Types.setFloorItemSetState,
|
||||
socket: Socket<any>
|
||||
) {
|
||||
const model = gltf.scene.clone();
|
||||
model.userData = { name: selectedItem.name, modelId: selectedItem.id };
|
||||
model.position.set(intersectPoint!.x, 3 + intersectPoint!.y, intersectPoint!.z);
|
||||
model.scale.set(...CONSTANTS.assetConfig.defaultScaleBeforeGsap);
|
||||
|
||||
model.traverse((child: any) => {
|
||||
if (child) {
|
||||
child.castShadow = true;
|
||||
child.receiveShadow = true;
|
||||
}
|
||||
});
|
||||
|
||||
itemsGroup.current.add(model);
|
||||
if (tempLoader.current) {
|
||||
(<any>tempLoader.current.material).dispose();
|
||||
(<any>tempLoader.current.geometry).dispose();
|
||||
itemsGroup.current.remove(tempLoader.current);
|
||||
tempLoader.current = undefined;
|
||||
}
|
||||
|
||||
const newFloorItem: Types.FloorItemType = {
|
||||
modeluuid: model.uuid,
|
||||
modelname: selectedItem.name,
|
||||
modelfileID: selectedItem.id,
|
||||
position: [intersectPoint!.x, intersectPoint!.y, intersectPoint!.z],
|
||||
rotation: { x: model.rotation.x, y: model.rotation.y, z: model.rotation.z, },
|
||||
isLocked: false,
|
||||
isVisible: true
|
||||
};
|
||||
|
||||
setFloorItems((prevItems) => {
|
||||
const updatedItems = [...(prevItems || []), newFloorItem];
|
||||
localStorage.setItem("FloorItems", JSON.stringify(updatedItems));
|
||||
return updatedItems;
|
||||
});
|
||||
|
||||
const email = localStorage.getItem("email");
|
||||
const organization = email ? email.split("@")[1].split(".")[0] : "default";
|
||||
|
||||
//REST
|
||||
|
||||
// await setFloorItemApi(
|
||||
// organization,
|
||||
// newFloorItem.modeluuid,
|
||||
// newFloorItem.modelname,
|
||||
// newFloorItem.position,
|
||||
// { "x": model.rotation.x, "y": model.rotation.y, "z": model.rotation.z },
|
||||
// newFloorItem.modelfileID!,
|
||||
// false,
|
||||
// true,
|
||||
// );
|
||||
|
||||
//SOCKET
|
||||
|
||||
const data = {
|
||||
organization,
|
||||
modeluuid: newFloorItem.modeluuid,
|
||||
modelname: newFloorItem.modelname,
|
||||
modelfileID: newFloorItem.modelfileID,
|
||||
position: newFloorItem.position,
|
||||
rotation: { x: model.rotation.x, y: model.rotation.y, z: model.rotation.z },
|
||||
isLocked: false,
|
||||
isVisible: true,
|
||||
socketId: socket.id,
|
||||
};
|
||||
|
||||
socket.emit("v1:FloorItems:set", data);
|
||||
|
||||
gsap.to(model.position, { y: newFloorItem.position[1], duration: 1.5, ease: "power2.out" });
|
||||
gsap.to(model.scale, { x: 1, y: 1, z: 1, duration: 1.5, ease: "power2.out", onComplete: () => { toast.success("Model Added!"); } });
|
||||
}
|
||||
|
||||
export default addAssetModel;
|
||||
153
app/src/modules/builder/geomentries/assets/assetManager.ts
Normal file
@@ -0,0 +1,153 @@
|
||||
import * as THREE from "three";
|
||||
import gsap from "gsap";
|
||||
import * as Types from "../../../../types/world/worldTypes";
|
||||
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
|
||||
import { initializeDB, retrieveGLTF, storeGLTF } from "../../../../utils/indexDB/idbUtils";
|
||||
import * as CONSTANTS from '../../../../types/world/worldConstants';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_MARKETPLACE_URL}`;
|
||||
let currentTaskId = 0; // Track the active task
|
||||
let activePromises = new Map<number, boolean>(); // Map to track task progress
|
||||
|
||||
export default async function assetManager(
|
||||
data: any,
|
||||
itemsGroup: Types.RefGroup,
|
||||
loader: GLTFLoader,
|
||||
) {
|
||||
const taskId = ++currentTaskId; // Increment taskId for each call
|
||||
activePromises.set(taskId, true); // Mark task as active
|
||||
|
||||
// console.log("Received message from worker:", data);
|
||||
|
||||
if (data.toRemove.length > 0) {
|
||||
data.toRemove.forEach((uuid: string) => {
|
||||
const item = itemsGroup.current.getObjectByProperty("uuid", uuid);
|
||||
if (item) {
|
||||
// Traverse and dispose of resources
|
||||
// item.traverse((child: THREE.Object3D) => {
|
||||
// if (child instanceof THREE.Mesh) {
|
||||
// if (child.geometry) child.geometry.dispose();
|
||||
// if (Array.isArray(child.material)) {
|
||||
// child.material.forEach((material) => {
|
||||
// if (material.map) material.map.dispose();
|
||||
// material.dispose();
|
||||
// });
|
||||
// } else if (child.material) {
|
||||
// if (child.material.map) child.material.map.dispose();
|
||||
// child.material.dispose();
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
|
||||
// Remove the object from the scene
|
||||
itemsGroup.current.remove(item);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (data.toAdd.length > 0) {
|
||||
await initializeDB();
|
||||
|
||||
for (const item of data.toAdd) {
|
||||
if (!activePromises.get(taskId)) return; // Stop processing if task is canceled
|
||||
|
||||
await new Promise<void>(async (resolve) => {
|
||||
const modelUrl = `${url_Backend_dwinzo}/api/v1/AssetFile/${item.modelfileID!}`;
|
||||
|
||||
// Check Three.js Cache
|
||||
const cachedModel = THREE.Cache.get(item.modelfileID!);
|
||||
if (cachedModel) {
|
||||
// console.log(`[Cache] Fetching ${item.modelname}`);
|
||||
processLoadedModel(cachedModel.scene.clone(), item, itemsGroup, resolve);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check IndexedDB
|
||||
const indexedDBModel = await retrieveGLTF(item.modelfileID!);
|
||||
if (indexedDBModel) {
|
||||
// console.log(`[IndexedDB] Fetching ${item.modelname}`);
|
||||
const blobUrl = URL.createObjectURL(indexedDBModel);
|
||||
loader.load(
|
||||
blobUrl,
|
||||
(gltf) => {
|
||||
URL.revokeObjectURL(blobUrl);
|
||||
THREE.Cache.remove(blobUrl);
|
||||
THREE.Cache.add(item.modelfileID!, gltf); // Add to cache
|
||||
processLoadedModel(gltf.scene.clone(), item, itemsGroup, resolve);
|
||||
},
|
||||
undefined,
|
||||
(error) => {
|
||||
toast.error(`[IndexedDB] Error loading ${item.modelname}:`);
|
||||
resolve();
|
||||
}
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Fetch from Backend
|
||||
// console.log(`[Backend] Fetching ${item.modelname}`);
|
||||
loader.load(
|
||||
modelUrl,
|
||||
async (gltf) => {
|
||||
const modelBlob = await fetch(modelUrl).then((res) => res.blob());
|
||||
await storeGLTF(item.modelfileID!, modelBlob); // Store in IndexedDB
|
||||
THREE.Cache.add(item.modelfileID!, gltf); // Add to cache
|
||||
processLoadedModel(gltf.scene.clone(), item, itemsGroup, resolve);
|
||||
},
|
||||
undefined,
|
||||
(error) => {
|
||||
toast.error(`[Backend] Error loading ${item.modelname}:`);
|
||||
resolve();
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
function processLoadedModel(
|
||||
gltf: any,
|
||||
item: Types.FloorItemType,
|
||||
itemsGroup: Types.RefGroup,
|
||||
resolve: () => void
|
||||
) {
|
||||
if (!activePromises.get(taskId)) return; // Stop processing if task is canceled
|
||||
|
||||
const existingModel = itemsGroup.current.getObjectByProperty("uuid", item.modeluuid);
|
||||
if (existingModel) {
|
||||
// console.log(`Model ${item.modelname} already exists in the scene.`);
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
const model = gltf;
|
||||
model.uuid = item.modeluuid;
|
||||
model.userData = { name: item.modelname, modelId: item.modelfileID };
|
||||
model.scale.set(...CONSTANTS.assetConfig.defaultScaleBeforeGsap);
|
||||
model.position.set(...item.position);
|
||||
model.rotation.set(item.rotation.x, item.rotation.y, item.rotation.z);
|
||||
|
||||
model.traverse((child: any) => {
|
||||
if (child.isMesh) {
|
||||
// Clone the material to ensure changes are independent
|
||||
// child.material = child.material.clone();
|
||||
|
||||
child.castShadow = true;
|
||||
child.receiveShadow = true;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
itemsGroup?.current?.add(model);
|
||||
|
||||
gsap.to(model.position, { y: item.position[1], duration: 1.5, ease: "power2.out" });
|
||||
gsap.to(model.scale, { x: 1, y: 1, z: 1, duration: 0.5, ease: "power2.out", onStart: resolve, });
|
||||
}
|
||||
}
|
||||
|
||||
activePromises.delete(taskId); // Mark task as complete
|
||||
}
|
||||
|
||||
// Cancel ongoing task when new call arrives
|
||||
export function cancelOngoingTasks() {
|
||||
activePromises.clear(); // Clear all ongoing tasks
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import * as Types from "../../../../types/world/worldTypes";
|
||||
|
||||
let lastUpdateTime = 0;
|
||||
|
||||
export default function assetVisibility(
|
||||
itemsGroup: Types.RefGroup,
|
||||
cameraPosition: Types.Vector3,
|
||||
renderDistance: Types.Number,
|
||||
throttleTime = 100
|
||||
): void {
|
||||
const now = performance.now();
|
||||
if (now - lastUpdateTime < throttleTime) return;
|
||||
lastUpdateTime = now;
|
||||
|
||||
if (!itemsGroup?.current || !cameraPosition) return;
|
||||
|
||||
itemsGroup.current.children.forEach((child) => {
|
||||
const Distance = cameraPosition.distanceTo(child.position);
|
||||
if (Distance <= renderDistance) {
|
||||
child.visible = true;
|
||||
} else {
|
||||
child.visible = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
import * as THREE from 'three';
|
||||
|
||||
import * as Types from "../../../../types/world/worldTypes";
|
||||
|
||||
function DeletableHoveredFloorItems(
|
||||
state: Types.ThreeState,
|
||||
itemsGroup: Types.RefGroup,
|
||||
hoveredDeletableFloorItem: Types.RefMesh,
|
||||
setDeletableFloorItem: any
|
||||
): void {
|
||||
|
||||
////////// Altering the color of the hovered GLTF item during the Deletion time //////////
|
||||
|
||||
state.raycaster.setFromCamera(state.pointer, state.camera);
|
||||
const intersects = state.raycaster.intersectObjects(itemsGroup.current.children, true);
|
||||
|
||||
if (intersects.length > 0) {
|
||||
if (intersects[0].object.name === "Pole") {
|
||||
return;
|
||||
}
|
||||
if (hoveredDeletableFloorItem.current) {
|
||||
hoveredDeletableFloorItem.current = undefined;
|
||||
setDeletableFloorItem(null);
|
||||
}
|
||||
let currentObject = intersects[0].object;
|
||||
|
||||
while (currentObject) {
|
||||
if (currentObject.name === "Scene") {
|
||||
hoveredDeletableFloorItem.current = currentObject as THREE.Mesh;
|
||||
setDeletableFloorItem(currentObject);
|
||||
break;
|
||||
}
|
||||
currentObject = currentObject.parent as THREE.Object3D;
|
||||
}
|
||||
} else {
|
||||
if (hoveredDeletableFloorItem.current) {
|
||||
hoveredDeletableFloorItem.current = undefined;
|
||||
setDeletableFloorItem(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default DeletableHoveredFloorItems;
|
||||
@@ -0,0 +1,82 @@
|
||||
import { toast } from 'react-toastify';
|
||||
import * as THREE from 'three';
|
||||
|
||||
import * as Types from "../../../../types/world/worldTypes";
|
||||
import { getFloorItems } from '../../../../services/factoryBuilder/assest/floorAsset/getFloorItemsApi';
|
||||
// import { deleteFloorItem } from '../../../../services/factoryBuilder/assest/floorAsset/deleteFloorItemApi';
|
||||
import { Socket } from 'socket.io-client';
|
||||
|
||||
async function DeleteFloorItems(
|
||||
itemsGroup: Types.RefGroup,
|
||||
hoveredDeletableFloorItem: Types.RefMesh,
|
||||
setFloorItems: Types.setFloorItemSetState,
|
||||
socket: Socket<any>
|
||||
): Promise<void> {
|
||||
|
||||
////////// Deleting the hovered Floor GLTF from the scene (itemsGroup.current) and from the floorItems and also update it in the localstorage //////////
|
||||
|
||||
if (hoveredDeletableFloorItem.current) {
|
||||
|
||||
const email = localStorage.getItem('email')
|
||||
const organization = (email!.split("@")[1]).split(".")[0];
|
||||
|
||||
const items = await getFloorItems(organization);
|
||||
const removedItem = items.find(
|
||||
(item: { modeluuid: string }) => item.modeluuid === hoveredDeletableFloorItem.current?.uuid
|
||||
);
|
||||
|
||||
if (!removedItem) {
|
||||
return
|
||||
}
|
||||
|
||||
//REST
|
||||
|
||||
// const response = await deleteFloorItem(organization, removedItem.modeluuid, removedItem.modelname);
|
||||
|
||||
//SOCKET
|
||||
|
||||
const data = {
|
||||
organization: organization,
|
||||
modeluuid: removedItem.modeluuid,
|
||||
modelname: removedItem.modelname,
|
||||
socketId: socket.id
|
||||
}
|
||||
|
||||
const response = socket.emit('v1:FloorItems:delete', data)
|
||||
|
||||
if (response) {
|
||||
const updatedItems = items.filter(
|
||||
(item: { modeluuid: string }) => item.modeluuid !== hoveredDeletableFloorItem.current?.uuid
|
||||
);
|
||||
|
||||
const storedItems = JSON.parse(localStorage.getItem("FloorItems") || '[]');
|
||||
const updatedStoredItems = storedItems.filter((item: { modeluuid: string }) => item.modeluuid !== hoveredDeletableFloorItem.current?.uuid);
|
||||
localStorage.setItem("FloorItems", JSON.stringify(updatedStoredItems));
|
||||
|
||||
if (hoveredDeletableFloorItem.current) {
|
||||
// Traverse and dispose of resources
|
||||
hoveredDeletableFloorItem.current.traverse((child: THREE.Object3D) => {
|
||||
if (child instanceof THREE.Mesh) {
|
||||
if (child.geometry) child.geometry.dispose();
|
||||
if (Array.isArray(child.material)) {
|
||||
child.material.forEach((material) => {
|
||||
if (material.map) material.map.dispose();
|
||||
material.dispose();
|
||||
});
|
||||
} else if (child.material) {
|
||||
if (child.material.map) child.material.map.dispose();
|
||||
child.material.dispose();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Remove the object from the scene
|
||||
itemsGroup.current.remove(hoveredDeletableFloorItem.current);
|
||||
}
|
||||
setFloorItems(updatedItems);
|
||||
toast.success("Model Removed!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default DeleteFloorItems;
|
||||
29
app/src/modules/builder/geomentries/assets/tempLoader.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import * as THREE from 'three';
|
||||
|
||||
import * as Types from "../../../../types/world/worldTypes";
|
||||
|
||||
function TempLoader(
|
||||
intersectPoint: Types.Vector3,
|
||||
isTempLoader: Types.RefBoolean,
|
||||
tempLoader: Types.RefMesh,
|
||||
itemsGroup: Types.RefGroup
|
||||
): void {
|
||||
|
||||
////////// Temporary Loader that indicates the gltf is being loaded //////////
|
||||
|
||||
////////// Bug: Can't Load More than one TempLoader if done, it won't leave the scene //////////
|
||||
|
||||
if (tempLoader.current) {
|
||||
itemsGroup.current.remove(tempLoader.current);
|
||||
}
|
||||
if (isTempLoader.current) {
|
||||
const cubeGeometry = new THREE.BoxGeometry(1, 1, 1);
|
||||
const cubeMaterial = new THREE.MeshBasicMaterial({ color: "white" });
|
||||
tempLoader.current = new THREE.Mesh(cubeGeometry, cubeMaterial);
|
||||
tempLoader.current.position.set(intersectPoint.x, 0.5 + intersectPoint.y, intersectPoint.z);
|
||||
itemsGroup.current.add(tempLoader.current);
|
||||
isTempLoader.current = false;
|
||||
}
|
||||
}
|
||||
|
||||
export default TempLoader;
|
||||
@@ -0,0 +1,64 @@
|
||||
import * as THREE from 'three';
|
||||
import * as Types from "../../../../types/world/worldTypes";
|
||||
import * as CONSTANTS from "../../../../types/world/worldConstants";
|
||||
|
||||
import texturePath from "../../../../assets/textures/floor/concreteFloorWorn001Diff2k.jpg";
|
||||
import normalPath from "../../../../assets/textures/floor/concreteFloorWorn001NorGl2k.jpg";
|
||||
|
||||
// Cache for materials
|
||||
const materialCache = new Map<string, THREE.Material>();
|
||||
|
||||
export default function addFloorToScene(
|
||||
shape: THREE.Shape,
|
||||
layer: number,
|
||||
floorGroup: Types.RefGroup,
|
||||
userData: any,
|
||||
) {
|
||||
const textureLoader = new THREE.TextureLoader();
|
||||
|
||||
const textureScale = CONSTANTS.floorConfig.textureScale;
|
||||
|
||||
const materialKey = `floorMaterial_${textureScale}`;
|
||||
|
||||
let material: THREE.Material;
|
||||
|
||||
if (materialCache.has(materialKey)) {
|
||||
material = materialCache.get(materialKey) as THREE.Material;
|
||||
} else {
|
||||
const floorTexture = textureLoader.load(texturePath);
|
||||
const normalMap = textureLoader.load(normalPath);
|
||||
|
||||
floorTexture.wrapS = floorTexture.wrapT = THREE.RepeatWrapping;
|
||||
floorTexture.repeat.set(textureScale, textureScale);
|
||||
floorTexture.colorSpace = THREE.SRGBColorSpace;
|
||||
|
||||
normalMap.wrapS = normalMap.wrapT = THREE.RepeatWrapping;
|
||||
normalMap.repeat.set(textureScale, textureScale);
|
||||
|
||||
material = new THREE.MeshStandardMaterial({
|
||||
map: floorTexture,
|
||||
normalMap: normalMap,
|
||||
side: THREE.DoubleSide,
|
||||
});
|
||||
|
||||
materialCache.set(materialKey, material);
|
||||
}
|
||||
|
||||
const extrudeSettings = {
|
||||
depth: CONSTANTS.floorConfig.height,
|
||||
bevelEnabled: false,
|
||||
};
|
||||
|
||||
const geometry = new THREE.ExtrudeGeometry(shape, extrudeSettings);
|
||||
const mesh = new THREE.Mesh(geometry, material);
|
||||
|
||||
mesh.receiveShadow = true;
|
||||
mesh.position.y = layer;
|
||||
mesh.rotateX(Math.PI / 2);
|
||||
mesh.name = `Floor_Layer_${layer}`;
|
||||
|
||||
// Store UUIDs for debugging or future processing
|
||||
mesh.userData.uuids = userData;
|
||||
|
||||
floorGroup.current.add(mesh);
|
||||
}
|
||||
179
app/src/modules/builder/geomentries/floors/drawOnlyFloor.ts
Normal file
@@ -0,0 +1,179 @@
|
||||
import * as THREE from 'three';
|
||||
|
||||
import * as Types from "../../../../types/world/worldTypes";
|
||||
import * as CONSTANTS from '../../../../types/world/worldConstants';
|
||||
|
||||
import addPointToScene from '../points/addPointToScene';
|
||||
import addLineToScene from '../lines/addLineToScene';
|
||||
import splitLine from '../lines/splitLine';
|
||||
import removeReferenceLine from '../lines/removeReferenceLine';
|
||||
import getClosestIntersection from '../lines/getClosestIntersection';
|
||||
import arrayLineToObject from '../lines/lineConvertions/arrayLineToObject';
|
||||
// import { setLine } from '../../../../services/factoryBuilder/lines/setLineApi';
|
||||
import { Socket } from 'socket.io-client';
|
||||
|
||||
async function drawOnlyFloor(
|
||||
raycaster: THREE.Raycaster,
|
||||
state: Types.ThreeState,
|
||||
camera: THREE.Camera,
|
||||
plane: Types.RefMesh,
|
||||
floorPlanGroupPoint: Types.RefGroup,
|
||||
snappedPoint: Types.RefVector3,
|
||||
isSnapped: Types.RefBoolean,
|
||||
isSnappedUUID: Types.RefString,
|
||||
line: Types.RefLine,
|
||||
ispreSnapped: Types.RefBoolean,
|
||||
anglesnappedPoint: Types.RefVector3,
|
||||
isAngleSnapped: Types.RefBoolean,
|
||||
onlyFloorline: Types.RefOnlyFloorLine,
|
||||
onlyFloorlines: Types.RefOnlyFloorLines,
|
||||
lines: Types.RefLines,
|
||||
floorPlanGroupLine: Types.RefGroup,
|
||||
floorPlanGroup: Types.RefGroup,
|
||||
ReferenceLineMesh: Types.RefMesh,
|
||||
LineCreated: Types.RefBoolean,
|
||||
currentLayerPoint: Types.RefMeshArray,
|
||||
dragPointControls: Types.RefDragControl,
|
||||
setNewLines: any,
|
||||
setDeletedLines: any,
|
||||
activeLayer: Types.Number,
|
||||
socket: Socket<any>
|
||||
): Promise<void> {
|
||||
|
||||
////////// Creating lines Based on the positions clicked //////////
|
||||
|
||||
if (!plane.current) return
|
||||
const intersects = raycaster.intersectObject(plane.current, true);
|
||||
const intersectsLines = raycaster.intersectObjects(floorPlanGroupLine.current.children, true);
|
||||
const intersectsPoint = raycaster.intersectObjects(floorPlanGroupPoint.current.children, true);
|
||||
const VisibleintersectsPoint = intersectsPoint.find(intersect => intersect.object.visible);
|
||||
const visibleIntersect = intersectsLines.find(intersect => intersect.object.visible && intersect.object.name !== CONSTANTS.lineConfig.referenceName);
|
||||
|
||||
if ((intersectsPoint.length === 0 || VisibleintersectsPoint === undefined) && intersectsLines.length > 0 && !isSnapped.current && !ispreSnapped.current) {
|
||||
|
||||
////////// Clicked on a preexisting Line //////////
|
||||
|
||||
if (visibleIntersect && (intersectsLines[0].object.userData.linePoints[0][3] === CONSTANTS.lineConfig.floorName || intersectsLines[0].object.userData.linePoints[0][3] === CONSTANTS.lineConfig.wallName)) {
|
||||
let pointColor, lineColor;
|
||||
if (intersectsLines[0].object.userData.linePoints[0][3] === CONSTANTS.lineConfig.wallName) {
|
||||
pointColor = CONSTANTS.pointConfig.wallOuterColor;
|
||||
lineColor = CONSTANTS.lineConfig.wallColor;
|
||||
} else {
|
||||
pointColor = CONSTANTS.pointConfig.floorOuterColor;
|
||||
lineColor = CONSTANTS.lineConfig.floorColor;
|
||||
}
|
||||
let IntersectsPoint = new THREE.Vector3(intersects[0].point.x, 0.01, intersects[0].point.z);
|
||||
if (isAngleSnapped.current && line.current.length > 0 && anglesnappedPoint.current) {
|
||||
IntersectsPoint = anglesnappedPoint.current;
|
||||
}
|
||||
if (visibleIntersect.object instanceof THREE.Mesh) {
|
||||
const ThroughPoint = (visibleIntersect.object.geometry.parameters.path).getPoints(CONSTANTS.lineConfig.lineIntersectionPoints);
|
||||
let intersectionPoint = getClosestIntersection(ThroughPoint, IntersectsPoint);
|
||||
|
||||
if (intersectionPoint) {
|
||||
|
||||
const newLines = splitLine(visibleIntersect, intersectionPoint, currentLayerPoint, floorPlanGroupPoint, dragPointControls, isSnappedUUID, lines, setDeletedLines, floorPlanGroupLine, socket, pointColor, lineColor, intersectsLines[0].object.userData.linePoints[0][3]);
|
||||
setNewLines([newLines[0], newLines[1]]);
|
||||
|
||||
(line.current as Types.Line).push([new THREE.Vector3(intersectionPoint.x, 0.01, intersectionPoint.z), isSnappedUUID.current!, activeLayer, CONSTANTS.lineConfig.floorName]);
|
||||
|
||||
if (line.current.length >= 2 && line.current[0] && line.current[1]) {
|
||||
lines.current.push(line.current as Types.Line);
|
||||
const data = arrayLineToObject(line.current as Types.Line);
|
||||
|
||||
const email = localStorage.getItem('email')
|
||||
const organization = (email!.split("@")[1]).split(".")[0];
|
||||
|
||||
//REST
|
||||
|
||||
// setLine(organization, data.layer!, data.line!, data.type!);
|
||||
|
||||
//SOCKET
|
||||
|
||||
const input = {
|
||||
organization: organization,
|
||||
layer: data.layer,
|
||||
line: data.line,
|
||||
type: data.type,
|
||||
socketId: socket.id
|
||||
}
|
||||
|
||||
socket.emit('v1:Line:create', input);
|
||||
|
||||
setNewLines([newLines[0], newLines[1], line.current]);
|
||||
onlyFloorline.current.push(line.current as Types.Line);
|
||||
onlyFloorlines.current.push(onlyFloorline.current);
|
||||
onlyFloorline.current = [];
|
||||
|
||||
addLineToScene(line.current[0][0], line.current[1][0], CONSTANTS.lineConfig.floorColor, line.current, floorPlanGroupLine);
|
||||
|
||||
removeReferenceLine(floorPlanGroup, ReferenceLineMesh, LineCreated, line);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (intersects.length > 0 && intersectsLines.length === 0) {
|
||||
|
||||
////////// Clicked on an empty place or a point //////////
|
||||
|
||||
let intersectionPoint = intersects[0].point;
|
||||
|
||||
if (isAngleSnapped.current && line.current.length > 0 && anglesnappedPoint.current) {
|
||||
intersectionPoint = anglesnappedPoint.current;
|
||||
}
|
||||
if (isSnapped.current && line.current.length > 0 && snappedPoint.current) {
|
||||
intersectionPoint = snappedPoint.current;
|
||||
}
|
||||
if (ispreSnapped.current && snappedPoint.current) {
|
||||
intersectionPoint = snappedPoint.current;
|
||||
}
|
||||
|
||||
if (!isSnapped.current && !ispreSnapped.current) {
|
||||
addPointToScene(intersectionPoint, CONSTANTS.pointConfig.floorOuterColor, currentLayerPoint, floorPlanGroupPoint, dragPointControls, isSnappedUUID, CONSTANTS.lineConfig.floorName);
|
||||
} else {
|
||||
ispreSnapped.current = false;
|
||||
isSnapped.current = false;
|
||||
}
|
||||
|
||||
(line.current as Types.Line).push([new THREE.Vector3(intersectionPoint.x, 0.01, intersectionPoint.z), isSnappedUUID.current!, activeLayer, CONSTANTS.lineConfig.floorName]);
|
||||
|
||||
if (line.current.length >= 2 && line.current[0] && line.current[1]) {
|
||||
onlyFloorline.current.push(line.current as Types.Line);
|
||||
lines.current.push(line.current as Types.Line);
|
||||
const data = arrayLineToObject(line.current as Types.Line);
|
||||
|
||||
const email = localStorage.getItem('email')
|
||||
const organization = (email!.split("@")[1]).split(".")[0];
|
||||
|
||||
//REST
|
||||
|
||||
// setLine(organization, data.layer!, data.line!, data.type!);
|
||||
|
||||
//SOCKET
|
||||
|
||||
const input = {
|
||||
organization: organization,
|
||||
layer: data.layer,
|
||||
line: data.line,
|
||||
type: data.type,
|
||||
socketId: socket.id
|
||||
}
|
||||
|
||||
socket.emit('v1:Line:create', input);
|
||||
|
||||
setNewLines([line.current]);
|
||||
addLineToScene(line.current[0][0], line.current[1][0], CONSTANTS.lineConfig.floorColor, line.current, floorPlanGroupLine);
|
||||
const lastPoint = line.current[line.current.length - 1];
|
||||
line.current = [lastPoint];
|
||||
}
|
||||
if (isSnapped.current) { ////////// Add this to stop the drawing mode after snapping //////////
|
||||
removeReferenceLine(floorPlanGroup, ReferenceLineMesh, LineCreated, line);
|
||||
onlyFloorlines.current.push(onlyFloorline.current);
|
||||
onlyFloorline.current = [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default drawOnlyFloor;
|
||||
50
app/src/modules/builder/geomentries/floors/loadFloor.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import * as THREE from 'three';
|
||||
import * as CONSTANTS from '../../../../types/world/worldConstants';
|
||||
import addRoofToScene from '../roofs/addRoofToScene';
|
||||
|
||||
import * as Types from "../../../../types/world/worldTypes";
|
||||
import loadOnlyFloors from './loadOnlyFloors';
|
||||
import addFloorToScene from './addFloorToScene';
|
||||
import getRoomsFromLines from '../lines/getRoomsFromLines';
|
||||
|
||||
async function loadFloor(
|
||||
lines: Types.RefLines,
|
||||
floorGroup: Types.RefGroup,
|
||||
): Promise<void> {
|
||||
|
||||
if (!floorGroup.current) return;
|
||||
|
||||
floorGroup.current.children = [];
|
||||
|
||||
if (lines.current.length > 2) {
|
||||
const linesByLayer = lines.current.reduce((acc: { [key: number]: any[] }, pair) => {
|
||||
const layer = pair[0][2];
|
||||
if (!acc[layer]) acc[layer] = [];
|
||||
acc[layer].push(pair);
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
for (const layer in linesByLayer) {
|
||||
// Only Floor Polygons
|
||||
loadOnlyFloors(floorGroup, linesByLayer, layer);
|
||||
|
||||
const rooms: Types.Rooms = await getRoomsFromLines({ current: linesByLayer[layer] });
|
||||
|
||||
rooms.forEach(({ coordinates: room, layer }) => {
|
||||
const userData = room.map(point => point.uuid);
|
||||
const shape = new THREE.Shape();
|
||||
shape.moveTo(room[0].position.x, room[0].position.z);
|
||||
room.forEach(point => shape.lineTo(point.position.x, point.position.z));
|
||||
shape.closePath();
|
||||
|
||||
// Floor Polygons
|
||||
addFloorToScene(shape, (layer - 1) * CONSTANTS.wallConfig.height, floorGroup, userData);
|
||||
|
||||
// Roof Polygons
|
||||
addRoofToScene(shape, (layer - 1) * CONSTANTS.wallConfig.height, userData, floorGroup);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default loadFloor;
|
||||
183
app/src/modules/builder/geomentries/floors/loadOnlyFloors.ts
Normal file
@@ -0,0 +1,183 @@
|
||||
import * as THREE from 'three';
|
||||
import * as turf from '@turf/turf';
|
||||
import * as CONSTANTS from '../../../../types/world/worldConstants';
|
||||
import * as Types from "../../../../types/world/worldTypes";
|
||||
|
||||
function loadOnlyFloors(
|
||||
floorGroup: Types.RefGroup,
|
||||
linesByLayer: any,
|
||||
layer: any,
|
||||
): void {
|
||||
|
||||
////////// Creating polygon floor based on the onlyFloorlines.current which does not add roof to it, The lines are still stored in Lines.current as well //////////
|
||||
|
||||
let floorsInLayer = linesByLayer[layer];
|
||||
floorsInLayer = floorsInLayer.filter((line: any) => line[0][3] && line[1][3] === CONSTANTS.lineConfig.floorName);
|
||||
const floorResult = floorsInLayer.map((pair: [THREE.Vector3, string, number, string][]) =>
|
||||
pair.map((point) => ({
|
||||
position: [point[0].x, point[0].z],
|
||||
uuid: point[1]
|
||||
}))
|
||||
);
|
||||
const FloorLineFeatures = floorResult.map((line: any) => turf.lineString(line.map((p: any) => p.position)));
|
||||
|
||||
function identifyPolygonsAndConnectedLines(FloorLineFeatures: any) {
|
||||
const floorpolygons = [];
|
||||
const connectedLines = [];
|
||||
const unprocessedLines = [...FloorLineFeatures]; // Copy the features
|
||||
|
||||
while (unprocessedLines.length > 0) {
|
||||
const currentLine = unprocessedLines.pop();
|
||||
const coordinates = currentLine.geometry.coordinates;
|
||||
|
||||
// Check if the line is closed (forms a polygon)
|
||||
if (
|
||||
coordinates[0][0] === coordinates[coordinates.length - 1][0] &&
|
||||
coordinates[0][1] === coordinates[coordinates.length - 1][1]
|
||||
) {
|
||||
floorpolygons.push(turf.polygon([coordinates])); // Add as a polygon
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if the line connects to another line
|
||||
let connected = false;
|
||||
for (let i = unprocessedLines.length - 1; i >= 0; i--) {
|
||||
const otherCoordinates = unprocessedLines[i].geometry.coordinates;
|
||||
|
||||
// Check if lines share a start or end point
|
||||
if (
|
||||
coordinates[0][0] === otherCoordinates[otherCoordinates.length - 1][0] &&
|
||||
coordinates[0][1] === otherCoordinates[otherCoordinates.length - 1][1]
|
||||
) {
|
||||
// Merge lines
|
||||
const mergedCoordinates = [...otherCoordinates, ...coordinates.slice(1)];
|
||||
unprocessedLines[i] = turf.lineString(mergedCoordinates);
|
||||
connected = true;
|
||||
break;
|
||||
} else if (
|
||||
coordinates[coordinates.length - 1][0] === otherCoordinates[0][0] &&
|
||||
coordinates[coordinates.length - 1][1] === otherCoordinates[0][1]
|
||||
) {
|
||||
// Merge lines
|
||||
const mergedCoordinates = [...coordinates, ...otherCoordinates.slice(1)];
|
||||
unprocessedLines[i] = turf.lineString(mergedCoordinates);
|
||||
connected = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!connected) {
|
||||
connectedLines.push(currentLine); // Add unconnected line as-is
|
||||
}
|
||||
}
|
||||
|
||||
return { floorpolygons, connectedLines };
|
||||
}
|
||||
|
||||
const { floorpolygons, connectedLines } = identifyPolygonsAndConnectedLines(FloorLineFeatures);
|
||||
|
||||
function convertConnectedLinesToPolygons(connectedLines: any) {
|
||||
return connectedLines.map((line: any) => {
|
||||
const coordinates = line.geometry.coordinates;
|
||||
|
||||
// If the line has more than two points, close the polygon
|
||||
if (coordinates.length > 2) {
|
||||
const firstPoint = coordinates[0];
|
||||
const lastPoint = coordinates[coordinates.length - 1];
|
||||
|
||||
// Check if already closed; if not, close it
|
||||
if (firstPoint[0] !== lastPoint[0] || firstPoint[1] !== lastPoint[1]) {
|
||||
coordinates.push(firstPoint);
|
||||
}
|
||||
|
||||
// Convert the closed line into a polygon
|
||||
return turf.polygon([coordinates]);
|
||||
}
|
||||
|
||||
// If not enough points for a polygon, return the line unchanged
|
||||
return line;
|
||||
});
|
||||
}
|
||||
|
||||
const convertedConnectedPolygons = convertConnectedLinesToPolygons(connectedLines);
|
||||
|
||||
if (convertedConnectedPolygons.length > 0) {
|
||||
const validPolygons = convertedConnectedPolygons.filter(
|
||||
(polygon: any) => polygon.geometry?.type === "Polygon"
|
||||
);
|
||||
|
||||
if (validPolygons.length > 0) {
|
||||
floorpolygons.push(...validPolygons);
|
||||
}
|
||||
}
|
||||
|
||||
function convertPolygonsToOriginalFormat(floorpolygons: any, originalLines: [THREE.Vector3, string, number, string][][]) {
|
||||
return floorpolygons.map((polygon: any) => {
|
||||
const coordinates = polygon.geometry.coordinates[0]; // Extract the coordinates array (assume it's a single polygon)
|
||||
|
||||
// Map each coordinate back to its original structure
|
||||
const mappedPoints = coordinates.map((coord: [number, number]) => {
|
||||
const [x, z] = coord;
|
||||
|
||||
// Find the original point matching this coordinate
|
||||
const originalPoint = originalLines.flat().find(([point]) => point.x === x && point.z === z);
|
||||
|
||||
if (!originalPoint) {
|
||||
throw new Error(`Original point for coordinate [${x}, ${z}] not found.`);
|
||||
}
|
||||
|
||||
return originalPoint;
|
||||
});
|
||||
|
||||
// Create pairs of consecutive points
|
||||
const pairs: typeof originalLines = [];
|
||||
for (let i = 0; i < mappedPoints.length - 1; i++) {
|
||||
pairs.push([mappedPoints[i], mappedPoints[i + 1]]);
|
||||
}
|
||||
|
||||
return pairs;
|
||||
});
|
||||
}
|
||||
|
||||
const convertedFloorPolygons: Types.OnlyFloorLines = convertPolygonsToOriginalFormat(floorpolygons, floorsInLayer);
|
||||
|
||||
convertedFloorPolygons.forEach((floor) => {
|
||||
const points: THREE.Vector3[] = [];
|
||||
|
||||
floor.forEach((lineSegment) => {
|
||||
const startPoint = lineSegment[0][0];
|
||||
points.push(new THREE.Vector3(startPoint.x, startPoint.y, startPoint.z));
|
||||
});
|
||||
|
||||
const lastLine = floor[floor.length - 1];
|
||||
const endPoint = lastLine[1][0];
|
||||
points.push(new THREE.Vector3(endPoint.x, endPoint.y, endPoint.z));
|
||||
|
||||
const shape = new THREE.Shape();
|
||||
shape.moveTo(points[0].x, points[0].z);
|
||||
|
||||
points.forEach(point => shape.lineTo(point.x, point.z));
|
||||
shape.closePath();
|
||||
|
||||
const extrudeSettings = {
|
||||
depth: CONSTANTS.floorConfig.height,
|
||||
bevelEnabled: false
|
||||
};
|
||||
|
||||
const geometry = new THREE.ExtrudeGeometry(shape, extrudeSettings);
|
||||
const material = new THREE.MeshStandardMaterial({ color: CONSTANTS.floorConfig.defaultColor, side: THREE.DoubleSide });
|
||||
const mesh = new THREE.Mesh(geometry, material);
|
||||
|
||||
mesh.castShadow = true;
|
||||
mesh.receiveShadow = true;
|
||||
|
||||
mesh.position.y = (floor[0][0][2] - 1) * CONSTANTS.wallConfig.height + 0.03;
|
||||
mesh.rotateX(Math.PI / 2);
|
||||
mesh.name = `Only_Floor_Line_${floor[0][0][2]}`;
|
||||
|
||||
mesh.userData = floor;
|
||||
floorGroup?.current?.add(mesh);
|
||||
});
|
||||
}
|
||||
|
||||
export default loadOnlyFloors;
|
||||
@@ -0,0 +1,24 @@
|
||||
import * as Types from "../../../../types/world/worldTypes";
|
||||
|
||||
function updateFloorLines(
|
||||
onlyFloorlines: Types.RefOnlyFloorLines,
|
||||
DragedPoint: Types.Mesh | { uuid: string, position: Types.Vector3 }
|
||||
): void {
|
||||
|
||||
////////// Update onlyFloorlines.current if it contains the dragged point //////////
|
||||
|
||||
onlyFloorlines.current.forEach((floorline) => {
|
||||
floorline.forEach((line) => {
|
||||
line.forEach((point) => {
|
||||
const [position, uuid] = point;
|
||||
if (uuid === DragedPoint.uuid) {
|
||||
position.x = DragedPoint.position.x;
|
||||
position.y = 0.01;
|
||||
position.z = DragedPoint.position.z;
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export default updateFloorLines;
|
||||
89
app/src/modules/builder/geomentries/layers/deleteLayer.ts
Normal file
@@ -0,0 +1,89 @@
|
||||
import { toast } from 'react-toastify';
|
||||
import RemoveConnectedLines from '../lines/removeConnectedLines';
|
||||
|
||||
import * as Types from '../../../../types/world/worldTypes';
|
||||
import { Socket } from 'socket.io-client';
|
||||
// import { deleteLayer } from '../../../../services/factoryBuilder/lines/deleteLayerApi';
|
||||
|
||||
async function DeleteLayer(
|
||||
removedLayer: Types.Number,
|
||||
lines: Types.RefLines,
|
||||
floorPlanGroupLine: Types.RefGroup,
|
||||
floorPlanGroupPoint: Types.RefGroup,
|
||||
onlyFloorlines: Types.RefOnlyFloorLines,
|
||||
floorGroup: Types.RefGroup,
|
||||
setDeletedLines: any,
|
||||
setRemovedLayer: Types.setRemoveLayerSetState,
|
||||
socket: Socket<any>
|
||||
): Promise<void> {
|
||||
|
||||
////////// Remove the Lines from the lines.current based on the removed layer and rearrange the layer number that are higher than the removed layer //////////
|
||||
|
||||
const removedLines: Types.Lines = lines.current.filter(line => line[0][2] === removedLayer);
|
||||
|
||||
const email = localStorage.getItem('email')
|
||||
const organization = (email!.split("@")[1]).split(".")[0];
|
||||
|
||||
//REST
|
||||
|
||||
// await deleteLayer(organization, removedLayer);
|
||||
|
||||
//SOCKET
|
||||
|
||||
const data = {
|
||||
organization: organization,
|
||||
layer: removedLayer,
|
||||
socketId: socket.id
|
||||
}
|
||||
|
||||
socket.emit('v1:Line:delete:layer', data);
|
||||
|
||||
////////// Remove Points and lines from the removed layer //////////
|
||||
|
||||
removedLines.forEach((line) => {
|
||||
line.forEach((removedPoint) => {
|
||||
RemoveConnectedLines(removedPoint[1], floorPlanGroupLine, floorPlanGroupPoint, setDeletedLines, lines);
|
||||
});
|
||||
});
|
||||
|
||||
////////// Update the remaining lines layer values in the userData and in lines.current //////////
|
||||
|
||||
let remaining = lines.current.filter(line => line[0][2] !== removedLayer);
|
||||
let updatedLines: Types.Lines = [];
|
||||
remaining.forEach(line => {
|
||||
let newLines: Types.Line = [...line];
|
||||
if (newLines[0][2] > removedLayer) {
|
||||
newLines[0][2] -= 1;
|
||||
newLines[1][2] -= 1;
|
||||
}
|
||||
|
||||
const matchingLine = floorPlanGroupLine.current.children.find(l => l.userData.linePoints[0][1] === line[0][1] && l.userData.linePoints[1][1] === line[1][1]);
|
||||
if (matchingLine) {
|
||||
const updatedUserData = matchingLine.userData;
|
||||
updatedUserData.linePoints[0][2] = newLines[0][2];
|
||||
updatedUserData.linePoints[1][2] = newLines[1][2];
|
||||
}
|
||||
updatedLines.push(newLines);
|
||||
});
|
||||
|
||||
lines.current = updatedLines;
|
||||
localStorage.setItem("Lines", JSON.stringify(lines.current));
|
||||
|
||||
////////// Also remove OnlyFloorLines and update it in localstorage //////////
|
||||
|
||||
onlyFloorlines.current = onlyFloorlines.current.filter((floor) => {
|
||||
return floor[0][0][2] !== removedLayer;
|
||||
});
|
||||
const meshToRemove: any = floorGroup.current?.children.find((mesh) =>
|
||||
mesh.name === `Only_Floor_Line_${removedLayer}`
|
||||
);
|
||||
if (meshToRemove) {
|
||||
(<any>meshToRemove.material).dispose();
|
||||
(<any>meshToRemove.geometry).dispose();
|
||||
floorGroup.current?.remove(meshToRemove);
|
||||
}
|
||||
|
||||
toast.success("Layer Removed!");
|
||||
setRemovedLayer(null);
|
||||
}
|
||||
export default DeleteLayer;
|
||||
@@ -0,0 +1,35 @@
|
||||
import * as Types from "../../../../types/world/worldTypes";
|
||||
|
||||
function Layer2DVisibility(
|
||||
activeLayer: Types.Number,
|
||||
floorPlanGroup: Types.RefGroup,
|
||||
floorPlanGroupLine: Types.RefGroup,
|
||||
floorPlanGroupPoint: Types.RefGroup,
|
||||
currentLayerPoint: Types.RefMeshArray,
|
||||
dragPointControls: Types.RefDragControl
|
||||
): void {
|
||||
|
||||
if (floorPlanGroup.current && dragPointControls.current) {
|
||||
currentLayerPoint.current = [];
|
||||
floorPlanGroupLine.current.children.forEach((line) => {
|
||||
const linePoints = line.userData.linePoints;
|
||||
|
||||
const point1 = floorPlanGroupPoint.current.getObjectByProperty('uuid', linePoints[0][1]) as Types.Mesh;
|
||||
const point2 = floorPlanGroupPoint.current.getObjectByProperty('uuid', linePoints[1][1]) as Types.Mesh;
|
||||
|
||||
if (linePoints[0][2] !== activeLayer && linePoints[1][2] !== activeLayer) {
|
||||
point1.visible = false;
|
||||
point2.visible = false;
|
||||
line.visible = false;
|
||||
} else {
|
||||
point1.visible = true;
|
||||
point2.visible = true;
|
||||
line.visible = true;
|
||||
currentLayerPoint.current.push(point1, point2);
|
||||
}
|
||||
});
|
||||
dragPointControls.current!.objects = currentLayerPoint.current;
|
||||
}
|
||||
}
|
||||
|
||||
export default Layer2DVisibility;
|
||||
24
app/src/modules/builder/geomentries/lines/addLineToScene.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import * as THREE from "three";
|
||||
import * as CONSTANTS from '../../../../types/world/worldConstants';
|
||||
import * as Types from "../../../../types/world/worldTypes";
|
||||
|
||||
function addLineToScene(
|
||||
start: Types.Vector3,
|
||||
end: Types.Vector3,
|
||||
colour: Types.Color,
|
||||
userData: Types.UserData,
|
||||
floorPlanGroupLine: Types.RefGroup
|
||||
): void {
|
||||
|
||||
////////// A function that creates and adds lines based on the start, end, and colour from the params, Also adds the userData in the mesh userData //////////
|
||||
|
||||
const path = new THREE.CatmullRomCurve3([start, end]);
|
||||
const geometry = new THREE.TubeGeometry(path, CONSTANTS.lineConfig.tubularSegments, CONSTANTS.lineConfig.radius, CONSTANTS.lineConfig.radialSegments, false);
|
||||
const material = new THREE.MeshBasicMaterial({ color: colour });
|
||||
const mesh = new THREE.Mesh(geometry, material);
|
||||
floorPlanGroupLine.current.add(mesh);
|
||||
|
||||
mesh.userData.linePoints = userData;
|
||||
}
|
||||
|
||||
export default addLineToScene;
|
||||
@@ -0,0 +1,98 @@
|
||||
import * as THREE from "three";
|
||||
import * as CONSTANTS from '../../../../types/world/worldConstants';
|
||||
import * as Types from "../../../../types/world/worldTypes";
|
||||
|
||||
function createAndMoveReferenceLine(
|
||||
point: Types.Vector3,
|
||||
cursorPosition: Types.Vector3,
|
||||
isSnapped: Types.RefBoolean,
|
||||
ispreSnapped: Types.RefBoolean,
|
||||
line: Types.RefLine,
|
||||
setRefTextUpdate: Types.NumberIncrementState,
|
||||
floorPlanGroup: Types.RefGroup,
|
||||
ReferenceLineMesh: Types.RefMesh,
|
||||
LineCreated: Types.RefBoolean,
|
||||
Tube: Types.RefTubeGeometry,
|
||||
anglesnappedPoint: Types.RefVector3,
|
||||
isAngleSnapped: Types.RefBoolean
|
||||
): void {
|
||||
|
||||
////////// Creating new and maintaining the old reference line and also snap the reference line based on its angle //////////
|
||||
|
||||
const startPoint = point;
|
||||
|
||||
const dx = cursorPosition.x - startPoint.x;
|
||||
const dz = cursorPosition.z - startPoint.z;
|
||||
let angle = Math.atan2(dz, dx);
|
||||
|
||||
angle = (angle * 180) / Math.PI;
|
||||
angle = (angle + 360) % 360;
|
||||
|
||||
const snapAngles = [0, 90, 180, 270, 360];
|
||||
const snapThreshold = 2.5;
|
||||
|
||||
const closestSnapAngle = snapAngles.reduce((prev, curr) =>
|
||||
Math.abs(curr - angle) < Math.abs(prev - angle) ? curr : prev
|
||||
);
|
||||
|
||||
if (!isSnapped.current && !ispreSnapped.current && line.current.length > 0) {
|
||||
if (Math.abs(closestSnapAngle - angle) <= snapThreshold) {
|
||||
const snappedAngleRad = (closestSnapAngle * Math.PI) / 180;
|
||||
const distance = Math.sqrt(dx * dx + dz * dz);
|
||||
const snappedX = startPoint.x + distance * Math.cos(snappedAngleRad);
|
||||
const snappedZ = startPoint.z + distance * Math.sin(snappedAngleRad);
|
||||
|
||||
if (
|
||||
cursorPosition.distanceTo(
|
||||
new THREE.Vector3(snappedX, 0.01, snappedZ)
|
||||
) < 2
|
||||
) {
|
||||
cursorPosition.set(snappedX, 0.01, snappedZ);
|
||||
isAngleSnapped.current = true;
|
||||
anglesnappedPoint.current = new THREE.Vector3(
|
||||
snappedX,
|
||||
0.01,
|
||||
snappedZ
|
||||
);
|
||||
} else {
|
||||
isAngleSnapped.current = false;
|
||||
anglesnappedPoint.current = null;
|
||||
}
|
||||
} else {
|
||||
isAngleSnapped.current = false;
|
||||
anglesnappedPoint.current = null;
|
||||
}
|
||||
} else {
|
||||
isAngleSnapped.current = false;
|
||||
anglesnappedPoint.current = null;
|
||||
}
|
||||
|
||||
if (!LineCreated.current) {
|
||||
setRefTextUpdate((prevUpdate) => prevUpdate - 1);
|
||||
const path = new THREE.LineCurve3(startPoint, cursorPosition);
|
||||
Tube.current = new THREE.TubeGeometry(path, CONSTANTS.lineConfig.tubularSegments, CONSTANTS.lineConfig.radius, CONSTANTS.lineConfig.radialSegments, false);
|
||||
const material = new THREE.MeshBasicMaterial({ color: CONSTANTS.lineConfig.helperColor });
|
||||
ReferenceLineMesh.current = new THREE.Mesh(Tube.current, material);
|
||||
ReferenceLineMesh.current.name = CONSTANTS.lineConfig.referenceName;
|
||||
ReferenceLineMesh.current.userData = {
|
||||
linePoints: { startPoint, cursorPosition },
|
||||
};
|
||||
floorPlanGroup.current?.add(ReferenceLineMesh.current);
|
||||
LineCreated.current = true;
|
||||
} else {
|
||||
if (ReferenceLineMesh.current) {
|
||||
const path = new THREE.LineCurve3(startPoint, new THREE.Vector3(cursorPosition.x, 0.01, cursorPosition.z));
|
||||
Tube.current = new THREE.TubeGeometry(path, CONSTANTS.lineConfig.tubularSegments, CONSTANTS.lineConfig.radius, CONSTANTS.lineConfig.radialSegments, false);
|
||||
|
||||
if (ReferenceLineMesh.current) {
|
||||
ReferenceLineMesh.current.userData = {
|
||||
linePoints: { startPoint, cursorPosition },
|
||||
};
|
||||
ReferenceLineMesh.current.geometry.dispose();
|
||||
ReferenceLineMesh.current.geometry = Tube.current;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default createAndMoveReferenceLine;
|
||||
88
app/src/modules/builder/geomentries/lines/deleteLine.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
import { Socket } from "socket.io-client";
|
||||
// import { deleteLineApi } from "../../../../services/factoryBuilder/lines/deleteLineApi";
|
||||
import * as Types from "../../../../types/world/worldTypes";
|
||||
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
function deleteLine(
|
||||
hoveredDeletableLine: Types.RefMesh,
|
||||
onlyFloorlines: Types.RefOnlyFloorLines,
|
||||
lines: Types.RefLines,
|
||||
floorPlanGroupLine: Types.RefGroup,
|
||||
floorPlanGroupPoint: Types.RefGroup,
|
||||
setDeletedLines: any,
|
||||
socket: Socket<any>
|
||||
): void {
|
||||
|
||||
////////// Deleting a line and the points if they are not connected to any other line //////////
|
||||
|
||||
if (!hoveredDeletableLine.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
const linePoints = hoveredDeletableLine.current.userData.linePoints;
|
||||
const connectedpoints = [linePoints[0][1], linePoints[1][1]];
|
||||
|
||||
const email = localStorage.getItem('email')
|
||||
const organization = (email!.split("@")[1]).split(".")[0];
|
||||
|
||||
//REST
|
||||
|
||||
// deleteLineApi(
|
||||
// organization,
|
||||
// [
|
||||
// { "uuid": linePoints[0][1] },
|
||||
// { "uuid": linePoints[1][1] }
|
||||
// ]
|
||||
// )
|
||||
|
||||
//SOCKET
|
||||
|
||||
const data = {
|
||||
organization: organization,
|
||||
line: [
|
||||
{ "uuid": linePoints[0][1] },
|
||||
{ "uuid": linePoints[1][1] }
|
||||
],
|
||||
socketId: socket.id
|
||||
}
|
||||
|
||||
socket.emit('v1:Line:delete', data);
|
||||
|
||||
|
||||
onlyFloorlines.current = onlyFloorlines.current.map(floorline =>
|
||||
floorline.filter(line => line[0][1] !== connectedpoints[0] && line[1][1] !== connectedpoints[1])
|
||||
).filter(floorline => floorline.length > 0);
|
||||
|
||||
lines.current = lines.current.filter(item => item !== linePoints);
|
||||
(<any>hoveredDeletableLine.current.material).dispose();
|
||||
(<any>hoveredDeletableLine.current.geometry).dispose();
|
||||
floorPlanGroupLine.current.remove(hoveredDeletableLine.current);
|
||||
setDeletedLines([linePoints]);
|
||||
|
||||
connectedpoints.forEach((pointUUID) => {
|
||||
let isConnected = false;
|
||||
floorPlanGroupLine.current.children.forEach((line) => {
|
||||
const linePoints = line.userData.linePoints;
|
||||
const uuid1 = linePoints[0][1];
|
||||
const uuid2 = linePoints[1][1];
|
||||
if (uuid1 === pointUUID || uuid2 === pointUUID) {
|
||||
isConnected = true;
|
||||
}
|
||||
});
|
||||
|
||||
if (!isConnected) {
|
||||
floorPlanGroupPoint.current.children.forEach((point: any) => {
|
||||
if (point.uuid === pointUUID) {
|
||||
(<any>point.material).dispose();
|
||||
(<any>point.geometry).dispose();
|
||||
floorPlanGroupPoint.current.remove(point);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
toast.success("Line Removed!");
|
||||
}
|
||||
|
||||
export default deleteLine;
|
||||
90
app/src/modules/builder/geomentries/lines/distanceText.tsx
Normal file
@@ -0,0 +1,90 @@
|
||||
import { useEffect, useState } from "react"
|
||||
import { getLines } from "../../../../services/factoryBuilder/lines/getLinesApi";
|
||||
import * as THREE from "three";
|
||||
import { useActiveLayer, useDeletedLines, useNewLines, useToggleView } from "../../../../store/store";
|
||||
import objectLinesToArray from "./lineConvertions/objectLinesToArray";
|
||||
import { Html } from "@react-three/drei";
|
||||
import * as Types from "../../../../types/world/worldTypes";
|
||||
|
||||
const DistanceText = () => {
|
||||
const [lines, setLines] = useState<{ distance: string; position: THREE.Vector3; userData: Types.Line; layer: string }[]>([]);
|
||||
const { activeLayer } = useActiveLayer();
|
||||
const { toggleView } = useToggleView();
|
||||
const { newLines, setNewLines } = useNewLines();
|
||||
const { deletedLines, setDeletedLines } = useDeletedLines();
|
||||
|
||||
useEffect(() => {
|
||||
const email = localStorage.getItem('email')
|
||||
if (!email) return;
|
||||
const organization = (email.split("@")[1]).split(".")[0];
|
||||
|
||||
getLines(organization).then((data) => {
|
||||
data = objectLinesToArray(data);
|
||||
|
||||
const lines = data.filter((line: Types.Line) => line[0][2] === activeLayer)
|
||||
.map((line: Types.Line) => {
|
||||
const point1 = new THREE.Vector3(line[0][0].x, line[0][0].y, line[0][0].z);
|
||||
const point2 = new THREE.Vector3(line[1][0].x, line[1][0].y, line[1][0].z);
|
||||
const distance = point1.distanceTo(point2);
|
||||
const midpoint = new THREE.Vector3().addVectors(point1, point2).divideScalar(2);
|
||||
return {
|
||||
distance: distance.toFixed(1),
|
||||
position: midpoint,
|
||||
userData: line,
|
||||
layer: activeLayer,
|
||||
};
|
||||
});
|
||||
setLines(lines)
|
||||
})
|
||||
}, [activeLayer])
|
||||
|
||||
useEffect(() => {
|
||||
if (newLines.length > 0) {
|
||||
if (newLines[0][0][2] !== activeLayer) return;
|
||||
const newLinesData = newLines.map((line: Types.Line) => {
|
||||
const point1 = new THREE.Vector3(line[0][0].x, line[0][0].y, line[0][0].z);
|
||||
const point2 = new THREE.Vector3(line[1][0].x, line[1][0].y, line[1][0].z);
|
||||
const distance = point1.distanceTo(point2);
|
||||
const midpoint = new THREE.Vector3().addVectors(point1, point2).divideScalar(2);
|
||||
|
||||
return {
|
||||
distance: distance.toFixed(1),
|
||||
position: midpoint,
|
||||
userData: line,
|
||||
layer: activeLayer,
|
||||
};
|
||||
});
|
||||
setLines((prevLines) => [...prevLines, ...newLinesData]);
|
||||
setNewLines([]);
|
||||
}
|
||||
}, [newLines, activeLayer]);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if ((deletedLines as Types.Lines).length > 0) {
|
||||
setLines((prevLines) =>
|
||||
prevLines.filter(
|
||||
(line) => !deletedLines.some((deletedLine: any) => deletedLine[0][1] === line.userData[0][1] && deletedLine[1][1] === line.userData[1][1])
|
||||
)
|
||||
);
|
||||
setDeletedLines([]);
|
||||
}
|
||||
}, [deletedLines]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{toggleView && (
|
||||
<group name='Distance_Text'>
|
||||
{lines.map((text) => (
|
||||
<Html key={`${text.userData[0][1]}_${text.userData[1][1]}`} transform sprite userData={text.userData} scale={5} position={[text.position.x, 1, text.position.z]} style={{ pointerEvents: 'none' }} >
|
||||
<div key={`${text.userData[0][1]}_${text.userData[1][1]}`} className={`Distance line-${text.userData[0][1]}_${text.userData[1][1]}_${text.layer}`} >{text.distance} m</div>
|
||||
</Html>
|
||||
))}
|
||||
</group>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
export default DistanceText;
|
||||
167
app/src/modules/builder/geomentries/lines/drawWall.ts
Normal file
@@ -0,0 +1,167 @@
|
||||
import * as THREE from 'three';
|
||||
import * as CONSTANTS from '../../../../types/world/worldConstants';
|
||||
|
||||
import addPointToScene from '../points/addPointToScene';
|
||||
import addLineToScene from './addLineToScene';
|
||||
import splitLine from './splitLine';
|
||||
import removeReferenceLine from './removeReferenceLine';
|
||||
import getClosestIntersection from './getClosestIntersection';
|
||||
|
||||
import * as Types from "../../../../types/world/worldTypes";
|
||||
import arrayLineToObject from './lineConvertions/arrayLineToObject';
|
||||
// import { setLine } from '../../../../services/factoryBuilder/lines/setLineApi';
|
||||
import { Socket } from 'socket.io-client';
|
||||
|
||||
async function drawWall(
|
||||
raycaster: THREE.Raycaster,
|
||||
plane: Types.RefMesh,
|
||||
floorPlanGroupPoint: Types.RefGroup,
|
||||
snappedPoint: Types.RefVector3,
|
||||
isSnapped: Types.RefBoolean,
|
||||
isSnappedUUID: Types.RefString,
|
||||
line: Types.RefLine,
|
||||
ispreSnapped: Types.RefBoolean,
|
||||
anglesnappedPoint: Types.RefVector3,
|
||||
isAngleSnapped: Types.RefBoolean,
|
||||
lines: Types.RefLines,
|
||||
floorPlanGroupLine: Types.RefGroup,
|
||||
floorPlanGroup: Types.RefGroup,
|
||||
ReferenceLineMesh: Types.RefMesh,
|
||||
LineCreated: Types.RefBoolean,
|
||||
currentLayerPoint: Types.RefMeshArray,
|
||||
dragPointControls: Types.RefDragControl,
|
||||
setNewLines: any,
|
||||
setDeletedLines: any,
|
||||
activeLayer: Types.Number,
|
||||
socket: Socket<any>
|
||||
): Promise<void> {
|
||||
|
||||
////////// Creating lines Based on the positions clicked //////////
|
||||
|
||||
////////// Allows the user lines that represents walls and roof, floor if forms a polygon //////////
|
||||
|
||||
|
||||
if (!plane.current) return
|
||||
let intersects = raycaster.intersectObject(plane.current, true);
|
||||
|
||||
let intersectsLines = raycaster.intersectObjects(floorPlanGroupLine.current.children, true);
|
||||
let intersectsPoint = raycaster.intersectObjects(floorPlanGroupPoint.current.children, true);
|
||||
|
||||
const VisibleintersectsPoint = intersectsPoint.find(intersect => intersect.object.visible);
|
||||
const visibleIntersect = intersectsLines.find(intersect => intersect.object.visible && intersect.object.name !== CONSTANTS.lineConfig.referenceName && intersect.object.userData.linePoints[0][3] === CONSTANTS.lineConfig.wallName);
|
||||
|
||||
if ((intersectsPoint.length === 0 || VisibleintersectsPoint === undefined) && intersectsLines.length > 0 && !isSnapped.current && !ispreSnapped.current) {
|
||||
|
||||
////////// Clicked on a preexisting Line //////////
|
||||
|
||||
if (visibleIntersect && intersects) {
|
||||
let IntersectsPoint = new THREE.Vector3(intersects[0].point.x, 0.01, intersects[0].point.z);
|
||||
|
||||
if (isAngleSnapped.current && anglesnappedPoint.current) {
|
||||
IntersectsPoint = anglesnappedPoint.current;
|
||||
}
|
||||
if (visibleIntersect.object instanceof THREE.Mesh) {
|
||||
const ThroughPoint = (visibleIntersect.object.geometry.parameters.path).getPoints(CONSTANTS.lineConfig.lineIntersectionPoints);
|
||||
let intersectionPoint = getClosestIntersection(ThroughPoint, IntersectsPoint);
|
||||
|
||||
if (intersectionPoint) {
|
||||
|
||||
const newLines = splitLine(visibleIntersect, intersectionPoint, currentLayerPoint, floorPlanGroupPoint, dragPointControls, isSnappedUUID, lines, setDeletedLines, floorPlanGroupLine, socket, CONSTANTS.pointConfig.wallOuterColor, CONSTANTS.lineConfig.wallColor, CONSTANTS.lineConfig.wallName);
|
||||
setNewLines([newLines[0], newLines[1]]);
|
||||
|
||||
(line.current as Types.Line).push([new THREE.Vector3(intersectionPoint.x, 0.01, intersectionPoint.z), isSnappedUUID.current!, activeLayer, CONSTANTS.lineConfig.wallName,]);
|
||||
|
||||
if (line.current.length >= 2 && line.current[0] && line.current[1]) {
|
||||
const data = arrayLineToObject(line.current as Types.Line);
|
||||
|
||||
const email = localStorage.getItem('email')
|
||||
const organization = (email!.split("@")[1]).split(".")[0];
|
||||
|
||||
//REST
|
||||
|
||||
// setLine(organization, data.layer!, data.line!, data.type!);
|
||||
|
||||
//SOCKET
|
||||
|
||||
const input = {
|
||||
organization: organization,
|
||||
layer: data.layer,
|
||||
line: data.line,
|
||||
type: data.type,
|
||||
socketId: socket.id
|
||||
}
|
||||
|
||||
socket.emit('v1:Line:create', input);
|
||||
|
||||
setNewLines([newLines[0], newLines[1], line.current]);
|
||||
lines.current.push(line.current as Types.Line);
|
||||
addLineToScene(line.current[0][0], line.current[1][0], CONSTANTS.lineConfig.wallColor, line.current, floorPlanGroupLine);
|
||||
let lastPoint = line.current[line.current.length - 1];
|
||||
line.current = [lastPoint];
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (intersects && intersects.length > 0) {
|
||||
|
||||
////////// Clicked on a emply place or a point //////////
|
||||
|
||||
let intersectionPoint = intersects[0].point;
|
||||
|
||||
if (isAngleSnapped.current && line.current.length > 0 && anglesnappedPoint.current) {
|
||||
intersectionPoint = anglesnappedPoint.current;
|
||||
}
|
||||
if (isSnapped.current && line.current.length > 0 && snappedPoint.current) {
|
||||
intersectionPoint = snappedPoint.current;
|
||||
}
|
||||
if (ispreSnapped.current && snappedPoint.current) {
|
||||
intersectionPoint = snappedPoint.current;
|
||||
}
|
||||
|
||||
if (!isSnapped.current && !ispreSnapped.current) {
|
||||
addPointToScene(intersectionPoint, CONSTANTS.pointConfig.wallOuterColor, currentLayerPoint, floorPlanGroupPoint, dragPointControls, isSnappedUUID, CONSTANTS.lineConfig.wallName);
|
||||
} else {
|
||||
ispreSnapped.current = false;
|
||||
isSnapped.current = false;
|
||||
}
|
||||
|
||||
(line.current as Types.Line).push([new THREE.Vector3(intersectionPoint.x, 0.01, intersectionPoint.z), isSnappedUUID.current!, activeLayer, CONSTANTS.lineConfig.wallName,]);
|
||||
|
||||
if (line.current.length >= 2 && line.current[0] && line.current[1]) {
|
||||
const data = arrayLineToObject(line.current as Types.Line);
|
||||
|
||||
const email = localStorage.getItem('email')
|
||||
const organization = (email!.split("@")[1]).split(".")[0];
|
||||
|
||||
//REST
|
||||
|
||||
// setLine(organization, data.layer!, data.line!, data.type!);
|
||||
|
||||
//SOCKET
|
||||
|
||||
const input = {
|
||||
organization: organization,
|
||||
layer: data.layer,
|
||||
line: data.line,
|
||||
type: data.type,
|
||||
socketId: socket.id
|
||||
}
|
||||
|
||||
socket.emit('v1:Line:create', input);
|
||||
|
||||
setNewLines([line.current])
|
||||
lines.current.push(line.current as Types.Line);
|
||||
addLineToScene(line.current[0][0], line.current[1][0], CONSTANTS.lineConfig.wallColor, line.current, floorPlanGroupLine);
|
||||
let lastPoint = line.current[line.current.length - 1];
|
||||
line.current = [lastPoint];
|
||||
}
|
||||
if (isSnapped.current) {
|
||||
removeReferenceLine(floorPlanGroup, ReferenceLineMesh, LineCreated, line);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default drawWall;
|
||||
@@ -0,0 +1,26 @@
|
||||
import * as THREE from 'three';
|
||||
|
||||
import * as Types from "../../../../types/world/worldTypes";
|
||||
|
||||
function getClosestIntersection(
|
||||
intersects: Types.Vector3Array,
|
||||
point: Types.Vector3
|
||||
): Types.Vector3 | null {
|
||||
|
||||
////////// A function that finds which point is closest from the intersects points that is given, Used in finding which point in a line is closest when clicked on a line during drawing //////////
|
||||
|
||||
let closestNewPoint: THREE.Vector3 | null = null;
|
||||
let minDistance = Infinity;
|
||||
|
||||
for (const intersect of intersects) {
|
||||
const distance = point.distanceTo(intersect);
|
||||
if (distance < minDistance) {
|
||||
minDistance = distance;
|
||||
closestNewPoint = intersect;
|
||||
}
|
||||
}
|
||||
|
||||
return closestNewPoint;
|
||||
}
|
||||
|
||||
export default getClosestIntersection;
|
||||
@@ -0,0 +1,86 @@
|
||||
import * as THREE from 'three';
|
||||
import * as turf from '@turf/turf';
|
||||
import * as CONSTANTS from '../../../../types/world/worldConstants';
|
||||
import * as Types from "../../../../types/world/worldTypes";
|
||||
|
||||
async function getRoomsFromLines(lines: Types.RefLines) {
|
||||
const rooms: Types.Rooms = [];
|
||||
|
||||
if (lines.current.length > 2) {
|
||||
const linesByLayer = lines.current.reduce((acc: { [key: number]: any[] }, pair) => {
|
||||
const layer = pair[0][2];
|
||||
if (!acc[layer]) acc[layer] = [];
|
||||
acc[layer].push(pair);
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
////////// Use turf.polygonize to create polygons from the line points //////////
|
||||
|
||||
for (const layer in linesByLayer) {
|
||||
|
||||
let linesInLayer = linesByLayer[layer];
|
||||
linesInLayer = linesInLayer.filter(line => line[0][3] && line[1][3] === CONSTANTS.lineConfig.wallName);
|
||||
const result = linesInLayer.map((pair: [THREE.Vector3, string, number, string][]) =>
|
||||
pair.map((point) => ({
|
||||
position: [point[0].x, point[0].z],
|
||||
uuid: point[1]
|
||||
}))
|
||||
);
|
||||
const lineFeatures = result.map(line => turf.lineString(line.map(p => p.position)));
|
||||
const polygons = turf.polygonize(turf.featureCollection(lineFeatures));
|
||||
|
||||
let union: any[] = [];
|
||||
|
||||
polygons.features.forEach((feature) => {
|
||||
union.push(feature);
|
||||
});
|
||||
|
||||
if (union.length > 1) {
|
||||
const unionResult = turf.union(turf.featureCollection(union));
|
||||
if (unionResult?.geometry.type === "MultiPolygon") {
|
||||
unionResult?.geometry.coordinates.forEach((poly) => {
|
||||
const Coordinates = poly[0].map(([x, z]) => {
|
||||
const matchingPoint = result.flat().find(r =>
|
||||
r.position[0].toFixed(10) === x.toFixed(10) &&
|
||||
r.position[1].toFixed(10) === z.toFixed(10)
|
||||
);
|
||||
return {
|
||||
position: new THREE.Vector3(x, 0, z),
|
||||
uuid: matchingPoint ? matchingPoint.uuid : ''
|
||||
};
|
||||
});
|
||||
rooms.push({ coordinates: Coordinates.reverse(), layer: parseInt(layer) });
|
||||
});
|
||||
} else if (unionResult?.geometry.type === "Polygon") {
|
||||
const Coordinates = unionResult?.geometry.coordinates[0].map(([x, z]) => {
|
||||
const matchingPoint = result.flat().find(r =>
|
||||
r.position[0].toFixed(10) === x.toFixed(10) &&
|
||||
r.position[1].toFixed(10) === z.toFixed(10)
|
||||
);
|
||||
return {
|
||||
position: new THREE.Vector3(x, 0, z),
|
||||
uuid: matchingPoint ? matchingPoint.uuid : ''
|
||||
};
|
||||
});
|
||||
rooms.push({ coordinates: Coordinates.reverse(), layer: parseInt(layer) });
|
||||
}
|
||||
} else if (union.length === 1) {
|
||||
const Coordinates = union[0].geometry.coordinates[0].map(([x, z]: [number, number]) => {
|
||||
const matchingPoint = result.flat().find(r =>
|
||||
r.position[0].toFixed(10) === x.toFixed(10) &&
|
||||
r.position[1].toFixed(10) === z.toFixed(10)
|
||||
);
|
||||
return {
|
||||
position: new THREE.Vector3(x, 0, z),
|
||||
uuid: matchingPoint ? matchingPoint.uuid : ''
|
||||
};
|
||||
});
|
||||
rooms.push({ coordinates: Coordinates, layer: parseInt(layer) });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return rooms;
|
||||
}
|
||||
|
||||
export default getRoomsFromLines;
|
||||
@@ -0,0 +1,24 @@
|
||||
import * as Types from "../../../../../types/world/worldTypes";
|
||||
|
||||
export default function arrayLineToObject(array: Types.Line) {
|
||||
if (!Array.isArray(array)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
// Extract common properties from the first point
|
||||
const commonLayer = array[0][2];
|
||||
const commonType = array[0][3];
|
||||
|
||||
// Map points into a structured format
|
||||
const line = array.map(([position, uuid]) => ({
|
||||
position,
|
||||
uuid,
|
||||
}));
|
||||
|
||||
// Create the final structured object
|
||||
return {
|
||||
layer: commonLayer,
|
||||
type: commonType,
|
||||
line,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import * as Types from "../../../../../types/world/worldTypes";
|
||||
|
||||
export default function arrayLinesToObject(array: Array<Types.Line>) {
|
||||
if (!Array.isArray(array)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return array.map((lineArray) => {
|
||||
if (!Array.isArray(lineArray)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Extract common properties from the first point
|
||||
const commonLayer = lineArray[0][2];
|
||||
const commonType = lineArray[0][3];
|
||||
|
||||
// Map points into a structured format
|
||||
const line = lineArray.map(([position, uuid]) => ({
|
||||
position,
|
||||
uuid,
|
||||
}));
|
||||
|
||||
// Create the final structured object
|
||||
return {
|
||||
layer: commonLayer,
|
||||
type: commonType,
|
||||
line,
|
||||
};
|
||||
}).filter((item) => item !== null); // Filter out invalid entries
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import * as THREE from 'three';
|
||||
|
||||
export default function objectLineToArray(structuredObject: any) {
|
||||
if (!structuredObject || !structuredObject.line) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Destructure common properties
|
||||
const { layer, type, line } = structuredObject;
|
||||
|
||||
// Map points back to the original array format
|
||||
return line.map(({ position, uuid }: any) => [new THREE.Vector3(position.x, position.y, position.z), uuid, layer, type]);
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import * as THREE from 'three';
|
||||
|
||||
export default function objectLinesToArray(structuredObjects: any): any {
|
||||
if (!Array.isArray(structuredObjects)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return structuredObjects.map((structuredObject) => {
|
||||
if (!structuredObject || !structuredObject.line) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const { layer, type, line } = structuredObject;
|
||||
|
||||
return line.map(({ position, uuid }: any) => {
|
||||
const vector = new THREE.Vector3(position.x, position.y, position.z);
|
||||
return [vector, uuid, layer, type];
|
||||
});
|
||||
});
|
||||
}
|
||||