python invoke sim
This commit is contained in:
parent
4d8cde2032
commit
f90e40459f
7
.env
7
.env
@ -17,4 +17,9 @@ POSTGRES_PORT=5432
|
||||
POSTGRES_HOST=postgres
|
||||
|
||||
SOCKET_BASE_ADDRESS=9091
|
||||
SOCKET_SERVICE_HOST=socket
|
||||
SOCKET_SERVICE_HOST=socket
|
||||
|
||||
|
||||
HOST_API=api.localhost
|
||||
HOST_WEB=localhost
|
||||
HOST_WSS=ws.localhost
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -1,3 +1,4 @@
|
||||
*.env
|
||||
*/*/node_modules
|
||||
*/*/.next/
|
||||
*/*/.next/
|
||||
*/*/*/.venv/
|
@ -80,13 +80,13 @@ services:
|
||||
front:
|
||||
container_name: dns-ui
|
||||
build:
|
||||
context: ./src/frontend
|
||||
dockerfile: Dockerfile
|
||||
context: ./src/usn-frontend
|
||||
dockerfile: test.Dockerfile
|
||||
env_file: ".env"
|
||||
volumes:
|
||||
- ./src/frontend:/app
|
||||
- ./src/usn-frontend:/app
|
||||
working_dir: /app
|
||||
command: ["npm", "start"]
|
||||
command: ["npm", "run", "dev"]
|
||||
ports:
|
||||
- "3000:3000"
|
||||
networks:
|
||||
|
26
nginx.conf
26
nginx.conf
@ -16,4 +16,30 @@ http {
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
}
|
||||
server {
|
||||
listen 80;
|
||||
server_name api.localhost;
|
||||
# add_header 'Access-Control-Allow-Origin' 'http://server' always;
|
||||
|
||||
location / {
|
||||
proxy_pass http://server:8080;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
}
|
||||
server {
|
||||
listen 80;
|
||||
server_name ws.localhost;
|
||||
# add_header 'Access-Control-Allow-Origin' 'http://socket' always;
|
||||
|
||||
location / {
|
||||
proxy_pass http://socket:9091;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,70 +0,0 @@
|
||||
# Getting Started with Create React App
|
||||
|
||||
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
|
||||
|
||||
## Available Scripts
|
||||
|
||||
In the project directory, you can run:
|
||||
|
||||
### `npm start`
|
||||
|
||||
Runs the app in the development mode.\
|
||||
Open [http://localhost:3000](http://localhost:3000) to view it in your browser.
|
||||
|
||||
The page will reload when you make changes.\
|
||||
You may also see any lint errors in the console.
|
||||
|
||||
### `npm test`
|
||||
|
||||
Launches the test runner in the interactive watch mode.\
|
||||
See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
|
||||
|
||||
### `npm run build`
|
||||
|
||||
Builds the app for production to the `build` folder.\
|
||||
It correctly bundles React in production mode and optimizes the build for the best performance.
|
||||
|
||||
The build is minified and the filenames include the hashes.\
|
||||
Your app is ready to be deployed!
|
||||
|
||||
See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
|
||||
|
||||
### `npm run eject`
|
||||
|
||||
**Note: this is a one-way operation. Once you `eject`, you can't go back!**
|
||||
|
||||
If you aren't satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
|
||||
|
||||
Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you're on your own.
|
||||
|
||||
You don't have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn't feel obligated to use this feature. However we understand that this tool wouldn't be useful if you couldn't customize it when you are ready for it.
|
||||
|
||||
## Learn More
|
||||
|
||||
You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
|
||||
|
||||
To learn React, check out the [React documentation](https://reactjs.org/).
|
||||
|
||||
### Code Splitting
|
||||
|
||||
This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting)
|
||||
|
||||
### Analyzing the Bundle Size
|
||||
|
||||
This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size)
|
||||
|
||||
### Making a Progressive Web App
|
||||
|
||||
This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app)
|
||||
|
||||
### Advanced Configuration
|
||||
|
||||
This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration)
|
||||
|
||||
### Deployment
|
||||
|
||||
This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment)
|
||||
|
||||
### `npm run build` fails to minify
|
||||
|
||||
This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify)
|
20283
src/frontend/package-lock.json
generated
20283
src/frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,48 +0,0 @@
|
||||
{
|
||||
"name": "dsn-application",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@mui/material": "^6.1.3",
|
||||
"@testing-library/jest-dom": "^5.17.0",
|
||||
"@testing-library/react": "^13.4.0",
|
||||
"@testing-library/user-event": "^13.5.0",
|
||||
"bootstrap-icons": "^1.11.3",
|
||||
"js-cookie": "^3.0.5",
|
||||
"qrcode.react": "^4.0.1",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-modal": "^3.16.1",
|
||||
"react-router-dom": "^6.26.2",
|
||||
"react-scripts": "5.0.1",
|
||||
"three": "^0.169.0",
|
||||
"web-vitals": "^2.1.4"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
"test": "react-scripts test",
|
||||
"eject": "react-scripts eject"
|
||||
},
|
||||
"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": {
|
||||
"sass": "^1.79.3"
|
||||
}
|
||||
}
|
Binary file not shown.
Binary file not shown.
Before Width: | Height: | Size: 57 KiB |
@ -1 +0,0 @@
|
||||
{"accessors":[],"asset":{"generator":"Open CASCADE Technology 7.6 [dev.opencascade.org]","version":"2.0"},"bufferViews":[],"buffers":[{"byteLength":0,"uri":"drone_normalized_gltf.bin"}],"meshes":[],"nodes":[],"scene":0,"scenes":[{"nodes":[]}]}
|
Binary file not shown.
Before Width: | Height: | Size: 3.8 KiB |
@ -1,47 +0,0 @@
|
||||
<!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" />
|
||||
<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" />
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css"
|
||||
/>
|
||||
<!--
|
||||
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>React App</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></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>
|
Binary file not shown.
Before Width: | Height: | Size: 5.2 KiB |
Binary file not shown.
Before Width: | Height: | Size: 9.4 KiB |
@ -1,25 +0,0 @@
|
||||
{
|
||||
"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"
|
||||
}
|
Binary file not shown.
Binary file not shown.
Before Width: | Height: | Size: 129 KiB |
@ -1,3 +0,0 @@
|
||||
# https://www.robotstxt.org/robotstxt.html
|
||||
User-agent: *
|
||||
Disallow:
|
@ -1,43 +0,0 @@
|
||||
.App {
|
||||
text-align: center;
|
||||
|
||||
}
|
||||
|
||||
.coler {
|
||||
background-color: #0d1117;
|
||||
color: azure;
|
||||
}
|
||||
.App-logo {
|
||||
height: 40vmin;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
.App-logo {
|
||||
animation: App-logo-spin infinite 20s linear;
|
||||
}
|
||||
}
|
||||
|
||||
.App-header {
|
||||
background-color: #282c34;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: calc(10px + 2vmin);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.App-link {
|
||||
color: #61dafb;
|
||||
}
|
||||
|
||||
@keyframes App-logo-spin {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
@ -1,72 +0,0 @@
|
||||
import React, { useState } from 'react';
|
||||
import DeviceGroups from "./pages/DeviceGroups";
|
||||
import Devices from "./pages/Devices";
|
||||
import Sidebar from "./Sidebar";
|
||||
import Dashboard from './pages/Dashboard';
|
||||
import Main from './pages/Main';
|
||||
import UserAccount from './pages/UserAccount'; // Импортируем компонент UserAccount
|
||||
import Login from './pages/Login'; // Импортируем страницу логина
|
||||
import Connections from './pages/Connections'; // Импортируем страницу подключений
|
||||
import PrevCalc from './pages/PrevCalc'; // Импортируем страницу подключений
|
||||
import Docs from './pages/Docs'; // Импортируем страницу подключений
|
||||
import './css/bootstrap-5.3.3-dist/css/bootstrap.min.css';
|
||||
import './App.css';
|
||||
import "bootstrap-icons/font/bootstrap-icons.css";
|
||||
import {removeCustomCookie} from './Services/Local'
|
||||
import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom';
|
||||
|
||||
const App = () => {
|
||||
const [isLoggedIn, setIsLoggedIn] = useState(false); // Статус авторизации
|
||||
const [activeTab, setActiveTab] = useState('main'); // По умолчанию активная вкладка "Карта"
|
||||
|
||||
// Функция для выхода из системы
|
||||
const handleLogout = () => {
|
||||
removeCustomCookie("userToken")
|
||||
setIsLoggedIn(false);
|
||||
};
|
||||
|
||||
const handleLogin = () => {
|
||||
setIsLoggedIn(true); // Устанавливаем авторизацию
|
||||
};
|
||||
|
||||
return (
|
||||
<Router>
|
||||
<Routes>
|
||||
<Route
|
||||
path="/login"
|
||||
element={isLoggedIn ? <Navigate to="/app" /> : <Login onLogin={handleLogin} />}
|
||||
/>
|
||||
<Route
|
||||
path="/app"
|
||||
element={isLoggedIn ? (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', height: '100vh' }}>
|
||||
<div style={{ display: 'flex', flex: 1 }}>
|
||||
<Sidebar
|
||||
onSelectTab={setActiveTab}
|
||||
activeTab={activeTab}
|
||||
onLogout={handleLogout} // Передаем функцию handleLogout через пропс onLogout
|
||||
/>
|
||||
<div className='coler' style={{ padding: '20px', flex: 1 }}>
|
||||
{activeTab === 'main' && <Main />} {/* Подключаем компонент Dashboard */}
|
||||
{activeTab === 'map' && <Dashboard />} {/* Подключаем компонент Dashboard */}
|
||||
{activeTab === 'connection' && <Connections />} {/* Страница подключений */}
|
||||
{activeTab === 'account' && <UserAccount />} {/* Подключаем компонент UserAccount */}
|
||||
{activeTab === 'groups' && <DeviceGroups />} {/* Группы устройств */}
|
||||
{activeTab === 'devices' && <Devices />} {/* Устройства */}
|
||||
{activeTab === 'prev_calc' && <PrevCalc />} {/* Устройства */}
|
||||
{activeTab === 'docs' && <Docs />} {/* Устройства */}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<Navigate to="/login" />
|
||||
)}
|
||||
/>
|
||||
{/* Перенаправляем на /login по умолчанию */}
|
||||
<Route path="*" element={<Navigate to="/login" />} />
|
||||
</Routes>
|
||||
</Router>
|
||||
);
|
||||
};
|
||||
|
||||
export default App;
|
@ -1,26 +0,0 @@
|
||||
|
||||
import Cookies from 'js-cookie';
|
||||
|
||||
|
||||
export const LocalSimulationJson = () => {
|
||||
|
||||
|
||||
return {
|
||||
"TODO": "JSON RETURN"
|
||||
}
|
||||
}
|
||||
|
||||
// Функция для установки куки
|
||||
export const setCustomCookie = (key, value, options = {}) => {
|
||||
Cookies.set(key, value, { ...options });
|
||||
};
|
||||
|
||||
// Функция для получения куки
|
||||
export const getCustomCookie = (key) => {
|
||||
return Cookies.get(key);
|
||||
};
|
||||
|
||||
// Функция для удаления куки
|
||||
export const removeCustomCookie = (key) => {
|
||||
Cookies.remove(key);
|
||||
};
|
@ -1,6 +0,0 @@
|
||||
|
||||
|
||||
|
||||
const InvokeStartModulation = () => {
|
||||
|
||||
}
|
@ -1,48 +0,0 @@
|
||||
|
||||
|
||||
|
||||
export const giveMeServerAddress = () => {
|
||||
if (process.env.SERVER_BASE_ADDRESS) {
|
||||
return process.env.SERVER_BASE_ADDRESS
|
||||
}
|
||||
return "http://localhost:8080";
|
||||
}
|
||||
|
||||
export const ServerRequest = async (req, type, data = null) => {
|
||||
let addr = giveMeServerAddress(); // получаем адрес сервера
|
||||
let options = {
|
||||
method: type, // тип запроса (GET, POST, PUT, DELETE и т.д.)
|
||||
headers: {
|
||||
'Content-Type': 'application/json', // заголовки для JSON данных
|
||||
}
|
||||
};
|
||||
|
||||
// Если данные есть и метод не GET, добавляем тело запроса
|
||||
if (data && type !== 'GET') {
|
||||
options.body = JSON.stringify(data);
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`${addr}${req}`, options); // выполняем запрос
|
||||
const responseData = await response.json(); // парсим JSON ответ
|
||||
|
||||
return {
|
||||
data: responseData, // данные от сервера
|
||||
status: response.status // код состояния ответа
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
return {
|
||||
data: null, // если ошибка, данных нет
|
||||
status: 500 // возвращаем код ошибки сервера
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export const giveMeServerWebsocketRequestAddress = async (groupHash = null) => {
|
||||
if (groupHash === null) {
|
||||
groupHash = Math.random().toString(12).substring(2); // генерируем хэш группы
|
||||
}
|
||||
const ADDRESS = "ws://localhost:8080/ws/connect?userToken=1&groupHash=" + groupHash; // адрес сервера
|
||||
return ADDRESS
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
|
||||
import {ServerRequest} from './Server'
|
||||
|
||||
export const RequestSaveOnServer = () => {
|
||||
// Запрос на сохранение результатов на сервере
|
||||
}
|
||||
|
||||
|
||||
export const RequestRunCalculations = () => {
|
||||
// Запрос на сохранение результатов на запуск вычислений
|
||||
}
|
||||
|
||||
export const RequestGetClientSettings = () => {
|
||||
// Запрос на получение клиентских настроек
|
||||
|
||||
}
|
||||
|
||||
export const RequestUpdateClientSettings = () => {
|
||||
// Запрос на обновление клиентских настроек
|
||||
|
||||
}
|
@ -1,49 +0,0 @@
|
||||
import { useEffect, useState, useRef } from 'react';
|
||||
|
||||
const useWebSocket = (userToken) => {
|
||||
const [message, setMessage] = useState(null);
|
||||
const [isConnected, setIsConnected] = useState(false);
|
||||
const ws = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!userToken) return;
|
||||
|
||||
const connectWebSocket = () => {
|
||||
const socketUrl = `ws://${process.env.SOCKET_SERVICE_HOST}:${process.env.SOCKET_BASE_ADDRESS}/ws/${userToken}`;
|
||||
ws.current = new WebSocket(socketUrl);
|
||||
|
||||
ws.current.onopen = () => {
|
||||
console.log('WebSocket connected');
|
||||
setIsConnected(true);
|
||||
};
|
||||
|
||||
ws.current.onmessage = (event) => {
|
||||
const data = event.data;
|
||||
console.log('Message received:', data);
|
||||
setMessage(data);
|
||||
};
|
||||
|
||||
ws.current.onclose = () => {
|
||||
console.log('WebSocket disconnected, attempting to reconnect...');
|
||||
setIsConnected(false);
|
||||
setTimeout(connectWebSocket, 3000); // Reconnect after 3 seconds
|
||||
};
|
||||
|
||||
ws.current.onerror = (error) => {
|
||||
console.error('WebSocket error:', error);
|
||||
};
|
||||
};
|
||||
|
||||
connectWebSocket();
|
||||
|
||||
return () => {
|
||||
if (ws.current) {
|
||||
ws.current.close();
|
||||
}
|
||||
};
|
||||
}, [userToken]);
|
||||
|
||||
return { message, isConnected };
|
||||
};
|
||||
|
||||
export default useWebSocket;
|
@ -1,172 +0,0 @@
|
||||
import { useEffect, useState, useRef } from 'react';
|
||||
|
||||
const useWebsocketConnection = (userToken, roomHash) => {
|
||||
const [websocketStruct, setWebsocketStruct] = useState(null);
|
||||
const socketRef = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
// Генерируем строку подключения
|
||||
const wsUrl = `ws://localhost:8080/ws/connect?userToken=${userToken}&groupHash=${roomHash}`;
|
||||
|
||||
// Создаем новое подключение WebSocket
|
||||
socketRef.current = new WebSocket(wsUrl);
|
||||
|
||||
// Обработчик открытия соединения
|
||||
socketRef.current.onopen = () => {
|
||||
console.log('WebSocket connection opened');
|
||||
};
|
||||
|
||||
// Обработчик получения сообщения
|
||||
socketRef.current.onmessage = (event) => {
|
||||
try {
|
||||
const data = JSON.parse(event.data);
|
||||
setWebsocketStruct((prevState) => {
|
||||
if (JSON.stringify(prevState) !== JSON.stringify(data)) {
|
||||
return data;
|
||||
}
|
||||
return prevState;
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error parsing WebSocket message:', error, event.data);
|
||||
}
|
||||
};
|
||||
|
||||
// Обработчик закрытия соединения
|
||||
socketRef.current.onclose = () => {
|
||||
console.log('WebSocket connection closed. Reconnecting...');
|
||||
reconnectWebSocket();
|
||||
};
|
||||
|
||||
// Обработчик ошибки
|
||||
socketRef.current.onerror = (error) => {
|
||||
console.error('WebSocket error:', error);
|
||||
};
|
||||
|
||||
// Очистка при размонтировании компонента
|
||||
return () => {
|
||||
if (socketRef.current) {
|
||||
socketRef.current.close();
|
||||
}
|
||||
};
|
||||
}, [userToken, roomHash]);
|
||||
|
||||
// Функция повторного подключения без разрыва связи
|
||||
const reconnectWebSocket = () => {
|
||||
const wsUrl = `ws://localhost:8080/ws/connect?userToken=${userToken}&groupHash=${roomHash}`;
|
||||
socketRef.current = new WebSocket(wsUrl);
|
||||
|
||||
socketRef.current.onopen = () => {
|
||||
console.log('WebSocket reconnected');
|
||||
};
|
||||
|
||||
socketRef.current.onmessage = (event) => {
|
||||
try {
|
||||
const data = JSON.parse(event.data);
|
||||
setWebsocketStruct((prevState) => {
|
||||
if (JSON.stringify(prevState) !== JSON.stringify(data)) {
|
||||
return data;
|
||||
}
|
||||
return prevState;
|
||||
});
|
||||
} catch (error) {
|
||||
if (event.data.substring(0, 2) === 'ws') {
|
||||
console.log(event.data)
|
||||
} else {
|
||||
console.error('Error parsing WebSocket message:', error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
socketRef.current.onclose = () => {
|
||||
console.log('WebSocket connection closed. Reconnecting...');
|
||||
reconnectWebSocket();
|
||||
};
|
||||
|
||||
socketRef.current.onerror = (error) => {
|
||||
console.error('WebSocket error:', error);
|
||||
};
|
||||
};
|
||||
|
||||
// Функция для отправки данных в WebSocket
|
||||
const sendMessage = async (message) => {
|
||||
if (socketRef.current && socketRef.current.readyState === WebSocket.OPEN) {
|
||||
console.log(JSON.stringify(message))
|
||||
socketRef.current.send(JSON.stringify(message));
|
||||
} else {
|
||||
console.error('WebSocket is not open. Unable to send message:', message);
|
||||
}
|
||||
};
|
||||
|
||||
return { websocketStruct, sendMessage };
|
||||
};
|
||||
|
||||
export const spawnJsonSignal =
|
||||
( signal,
|
||||
name,
|
||||
coords,
|
||||
antennaRadius,
|
||||
antennaDirections="0,0,0",
|
||||
modulation="",
|
||||
bandwidth=0,
|
||||
dataRate=0,
|
||||
wayPoints=[],
|
||||
speed=0,
|
||||
meshName=null
|
||||
) => {
|
||||
if (![1, 2].includes(signal)) {
|
||||
throw new Error('Invalid signal value. Expected 1 or 2.');
|
||||
}
|
||||
|
||||
if (typeof name !== 'string' || !name.trim()) {
|
||||
throw new Error('Invalid name. Expected a non-empty string.');
|
||||
}
|
||||
|
||||
return {
|
||||
signal,
|
||||
data: {
|
||||
name,
|
||||
params: {
|
||||
coords,
|
||||
antennaRadius,
|
||||
antennaDirections,
|
||||
modulation,
|
||||
bandwidth,
|
||||
dataRate,
|
||||
wayPoints,
|
||||
speed,
|
||||
meshName
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
export const DeleteSignal = (name) => {
|
||||
const signal = 3;
|
||||
|
||||
if (typeof name !== 'string' || !name.trim()) {
|
||||
throw new Error('Invalid name. Expected a non-empty string.');
|
||||
}
|
||||
|
||||
return {
|
||||
signal,
|
||||
data: {
|
||||
name,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const RunSimulatorSignal = (id) => {
|
||||
const signal = 100;
|
||||
|
||||
return {
|
||||
signal,
|
||||
data: {
|
||||
id,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
|
||||
export default useWebsocketConnection;
|
@ -1,58 +0,0 @@
|
||||
.sidebar {
|
||||
width: 200px;
|
||||
height: 120vh;
|
||||
background-color: #2c3e50;
|
||||
color: white;
|
||||
padding: 20px;
|
||||
box-shadow: 2px 0 5px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.sidebar h2 {
|
||||
font-size: 24px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.sidebar ul {
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.sidebar li {
|
||||
margin: 15px 0;
|
||||
cursor: pointer;
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
|
||||
.sidebar li:hover {
|
||||
background-color: #34495e;
|
||||
}
|
||||
|
||||
.sidebar li.active {
|
||||
background-color: #2980b9;
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* Стили для кнопки "Выйти" */
|
||||
.logout-button {
|
||||
display: block;
|
||||
margin-top: 20px;
|
||||
padding: 10px;
|
||||
width: 100%;
|
||||
background-color: #e74c3c;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
|
||||
.logout-button:hover {
|
||||
background-color: #c0392b;
|
||||
}
|
||||
|
||||
.logout-button:focus {
|
||||
outline: none;
|
||||
}
|
@ -1,55 +0,0 @@
|
||||
import React, { useState } from 'react';
|
||||
import './Sidebar.css';
|
||||
|
||||
const Sidebar = ({ onSelectTab, activeTab, onLogout }) => {
|
||||
const [isSubMenuOpen, setIsSubMenuOpen] = useState(false);
|
||||
|
||||
const toggleSubMenu = () => {
|
||||
setIsSubMenuOpen(!isSubMenuOpen);
|
||||
// Переключение на вкладку "Аккаунт пользователя"
|
||||
if (!isSubMenuOpen) {
|
||||
onSelectTab('account');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="sidebar">
|
||||
<h2>Меню</h2>
|
||||
<ul>
|
||||
<li className={activeTab === 'main' ? 'active' : ''} onClick={() => onSelectTab('main')}>
|
||||
Главная
|
||||
</li>
|
||||
<li className={activeTab === 'map' ? 'active' : ''} onClick={() => onSelectTab('map')}>
|
||||
Сессия 1
|
||||
</li>
|
||||
<li className={activeTab === 'connection' ? 'active' : ''} onClick={() => onSelectTab('connection')}>
|
||||
Настройки
|
||||
</li>
|
||||
<li className={activeTab === 'prev_calc' ? 'active' : ''} onClick={() => onSelectTab('prev_calc')}>
|
||||
Проведенные вычисления
|
||||
</li>
|
||||
<li className={activeTab === 'docs' ? 'active' : ''} onClick={() => onSelectTab('docs')}>
|
||||
Документация
|
||||
</li>
|
||||
<li className={activeTab === 'account' ? 'active' : ''} onClick={toggleSubMenu}>
|
||||
Аккаунт пользователя
|
||||
</li>
|
||||
{isSubMenuOpen && (
|
||||
<>
|
||||
<li className={activeTab === 'groups' ? 'active' : ''} onClick={() => onSelectTab('groups')}>
|
||||
Шаблоны
|
||||
</li>
|
||||
<li className={activeTab === 'devices' ? 'active' : ''} onClick={() => onSelectTab('devices')}>
|
||||
Действия
|
||||
</li>
|
||||
</>
|
||||
)}
|
||||
</ul>
|
||||
<button className="logout-button" onClick={onLogout}>
|
||||
Выйти
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Sidebar;
|
@ -1,13 +0,0 @@
|
||||
|
||||
.main__imager {
|
||||
height: 100%;
|
||||
/* background-image: url('../../../public/blueprints.jpg'); */
|
||||
}
|
||||
|
||||
.main__t_b {
|
||||
color: black;
|
||||
}
|
||||
|
||||
.title {
|
||||
|
||||
}
|
@ -1,48 +0,0 @@
|
||||
.device-groups {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.device-groups h2 {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.group-creation {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.group-creation input {
|
||||
padding: 5px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.group-creation button {
|
||||
padding: 5px 10px;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
li {
|
||||
margin-bottom: 20px;
|
||||
padding: 10px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.active {
|
||||
background-color: #e8f5e9;
|
||||
}
|
||||
|
||||
.inactive {
|
||||
background-color: #ffebee;
|
||||
}
|
||||
|
||||
li div {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
li button {
|
||||
margin-right: 10px;
|
||||
}
|
@ -1,38 +0,0 @@
|
||||
.devices {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.devices h2 {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.devices table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.devices table, .devices th, .devices td {
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.devices th, .devices td {
|
||||
padding: 8px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.devices th {
|
||||
background-color: #34495e;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.devices tr:hover {
|
||||
background-color: #f1f1f1;
|
||||
}
|
||||
|
||||
.buttons {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.buttons button {
|
||||
margin-right: 10px;
|
||||
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1,597 +0,0 @@
|
||||
/*!
|
||||
* Bootstrap Reboot v5.3.3 (https://getbootstrap.com/)
|
||||
* Copyright 2011-2024 The Bootstrap Authors
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
*/
|
||||
:root,
|
||||
[data-bs-theme=light] {
|
||||
--bs-blue: #0d6efd;
|
||||
--bs-indigo: #6610f2;
|
||||
--bs-purple: #6f42c1;
|
||||
--bs-pink: #d63384;
|
||||
--bs-red: #dc3545;
|
||||
--bs-orange: #fd7e14;
|
||||
--bs-yellow: #ffc107;
|
||||
--bs-green: #198754;
|
||||
--bs-teal: #20c997;
|
||||
--bs-cyan: #0dcaf0;
|
||||
--bs-black: #000;
|
||||
--bs-white: #fff;
|
||||
--bs-gray: #6c757d;
|
||||
--bs-gray-dark: #343a40;
|
||||
--bs-gray-100: #f8f9fa;
|
||||
--bs-gray-200: #e9ecef;
|
||||
--bs-gray-300: #dee2e6;
|
||||
--bs-gray-400: #ced4da;
|
||||
--bs-gray-500: #adb5bd;
|
||||
--bs-gray-600: #6c757d;
|
||||
--bs-gray-700: #495057;
|
||||
--bs-gray-800: #343a40;
|
||||
--bs-gray-900: #212529;
|
||||
--bs-primary: #0d6efd;
|
||||
--bs-secondary: #6c757d;
|
||||
--bs-success: #198754;
|
||||
--bs-info: #0dcaf0;
|
||||
--bs-warning: #ffc107;
|
||||
--bs-danger: #dc3545;
|
||||
--bs-light: #f8f9fa;
|
||||
--bs-dark: #212529;
|
||||
--bs-primary-rgb: 13, 110, 253;
|
||||
--bs-secondary-rgb: 108, 117, 125;
|
||||
--bs-success-rgb: 25, 135, 84;
|
||||
--bs-info-rgb: 13, 202, 240;
|
||||
--bs-warning-rgb: 255, 193, 7;
|
||||
--bs-danger-rgb: 220, 53, 69;
|
||||
--bs-light-rgb: 248, 249, 250;
|
||||
--bs-dark-rgb: 33, 37, 41;
|
||||
--bs-primary-text-emphasis: #052c65;
|
||||
--bs-secondary-text-emphasis: #2b2f32;
|
||||
--bs-success-text-emphasis: #0a3622;
|
||||
--bs-info-text-emphasis: #055160;
|
||||
--bs-warning-text-emphasis: #664d03;
|
||||
--bs-danger-text-emphasis: #58151c;
|
||||
--bs-light-text-emphasis: #495057;
|
||||
--bs-dark-text-emphasis: #495057;
|
||||
--bs-primary-bg-subtle: #cfe2ff;
|
||||
--bs-secondary-bg-subtle: #e2e3e5;
|
||||
--bs-success-bg-subtle: #d1e7dd;
|
||||
--bs-info-bg-subtle: #cff4fc;
|
||||
--bs-warning-bg-subtle: #fff3cd;
|
||||
--bs-danger-bg-subtle: #f8d7da;
|
||||
--bs-light-bg-subtle: #fcfcfd;
|
||||
--bs-dark-bg-subtle: #ced4da;
|
||||
--bs-primary-border-subtle: #9ec5fe;
|
||||
--bs-secondary-border-subtle: #c4c8cb;
|
||||
--bs-success-border-subtle: #a3cfbb;
|
||||
--bs-info-border-subtle: #9eeaf9;
|
||||
--bs-warning-border-subtle: #ffe69c;
|
||||
--bs-danger-border-subtle: #f1aeb5;
|
||||
--bs-light-border-subtle: #e9ecef;
|
||||
--bs-dark-border-subtle: #adb5bd;
|
||||
--bs-white-rgb: 255, 255, 255;
|
||||
--bs-black-rgb: 0, 0, 0;
|
||||
--bs-font-sans-serif: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", "Liberation Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
||||
--bs-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||
--bs-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));
|
||||
--bs-body-font-family: var(--bs-font-sans-serif);
|
||||
--bs-body-font-size: 1rem;
|
||||
--bs-body-font-weight: 400;
|
||||
--bs-body-line-height: 1.5;
|
||||
--bs-body-color: #212529;
|
||||
--bs-body-color-rgb: 33, 37, 41;
|
||||
--bs-body-bg: #fff;
|
||||
--bs-body-bg-rgb: 255, 255, 255;
|
||||
--bs-emphasis-color: #000;
|
||||
--bs-emphasis-color-rgb: 0, 0, 0;
|
||||
--bs-secondary-color: rgba(33, 37, 41, 0.75);
|
||||
--bs-secondary-color-rgb: 33, 37, 41;
|
||||
--bs-secondary-bg: #e9ecef;
|
||||
--bs-secondary-bg-rgb: 233, 236, 239;
|
||||
--bs-tertiary-color: rgba(33, 37, 41, 0.5);
|
||||
--bs-tertiary-color-rgb: 33, 37, 41;
|
||||
--bs-tertiary-bg: #f8f9fa;
|
||||
--bs-tertiary-bg-rgb: 248, 249, 250;
|
||||
--bs-heading-color: inherit;
|
||||
--bs-link-color: #0d6efd;
|
||||
--bs-link-color-rgb: 13, 110, 253;
|
||||
--bs-link-decoration: underline;
|
||||
--bs-link-hover-color: #0a58ca;
|
||||
--bs-link-hover-color-rgb: 10, 88, 202;
|
||||
--bs-code-color: #d63384;
|
||||
--bs-highlight-color: #212529;
|
||||
--bs-highlight-bg: #fff3cd;
|
||||
--bs-border-width: 1px;
|
||||
--bs-border-style: solid;
|
||||
--bs-border-color: #dee2e6;
|
||||
--bs-border-color-translucent: rgba(0, 0, 0, 0.175);
|
||||
--bs-border-radius: 0.375rem;
|
||||
--bs-border-radius-sm: 0.25rem;
|
||||
--bs-border-radius-lg: 0.5rem;
|
||||
--bs-border-radius-xl: 1rem;
|
||||
--bs-border-radius-xxl: 2rem;
|
||||
--bs-border-radius-2xl: var(--bs-border-radius-xxl);
|
||||
--bs-border-radius-pill: 50rem;
|
||||
--bs-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
|
||||
--bs-box-shadow-sm: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
|
||||
--bs-box-shadow-lg: 0 1rem 3rem rgba(0, 0, 0, 0.175);
|
||||
--bs-box-shadow-inset: inset 0 1px 2px rgba(0, 0, 0, 0.075);
|
||||
--bs-focus-ring-width: 0.25rem;
|
||||
--bs-focus-ring-opacity: 0.25;
|
||||
--bs-focus-ring-color: rgba(13, 110, 253, 0.25);
|
||||
--bs-form-valid-color: #198754;
|
||||
--bs-form-valid-border-color: #198754;
|
||||
--bs-form-invalid-color: #dc3545;
|
||||
--bs-form-invalid-border-color: #dc3545;
|
||||
}
|
||||
|
||||
[data-bs-theme=dark] {
|
||||
color-scheme: dark;
|
||||
--bs-body-color: #dee2e6;
|
||||
--bs-body-color-rgb: 222, 226, 230;
|
||||
--bs-body-bg: #212529;
|
||||
--bs-body-bg-rgb: 33, 37, 41;
|
||||
--bs-emphasis-color: #fff;
|
||||
--bs-emphasis-color-rgb: 255, 255, 255;
|
||||
--bs-secondary-color: rgba(222, 226, 230, 0.75);
|
||||
--bs-secondary-color-rgb: 222, 226, 230;
|
||||
--bs-secondary-bg: #343a40;
|
||||
--bs-secondary-bg-rgb: 52, 58, 64;
|
||||
--bs-tertiary-color: rgba(222, 226, 230, 0.5);
|
||||
--bs-tertiary-color-rgb: 222, 226, 230;
|
||||
--bs-tertiary-bg: #2b3035;
|
||||
--bs-tertiary-bg-rgb: 43, 48, 53;
|
||||
--bs-primary-text-emphasis: #6ea8fe;
|
||||
--bs-secondary-text-emphasis: #a7acb1;
|
||||
--bs-success-text-emphasis: #75b798;
|
||||
--bs-info-text-emphasis: #6edff6;
|
||||
--bs-warning-text-emphasis: #ffda6a;
|
||||
--bs-danger-text-emphasis: #ea868f;
|
||||
--bs-light-text-emphasis: #f8f9fa;
|
||||
--bs-dark-text-emphasis: #dee2e6;
|
||||
--bs-primary-bg-subtle: #031633;
|
||||
--bs-secondary-bg-subtle: #161719;
|
||||
--bs-success-bg-subtle: #051b11;
|
||||
--bs-info-bg-subtle: #032830;
|
||||
--bs-warning-bg-subtle: #332701;
|
||||
--bs-danger-bg-subtle: #2c0b0e;
|
||||
--bs-light-bg-subtle: #343a40;
|
||||
--bs-dark-bg-subtle: #1a1d20;
|
||||
--bs-primary-border-subtle: #084298;
|
||||
--bs-secondary-border-subtle: #41464b;
|
||||
--bs-success-border-subtle: #0f5132;
|
||||
--bs-info-border-subtle: #087990;
|
||||
--bs-warning-border-subtle: #997404;
|
||||
--bs-danger-border-subtle: #842029;
|
||||
--bs-light-border-subtle: #495057;
|
||||
--bs-dark-border-subtle: #343a40;
|
||||
--bs-heading-color: inherit;
|
||||
--bs-link-color: #6ea8fe;
|
||||
--bs-link-hover-color: #8bb9fe;
|
||||
--bs-link-color-rgb: 110, 168, 254;
|
||||
--bs-link-hover-color-rgb: 139, 185, 254;
|
||||
--bs-code-color: #e685b5;
|
||||
--bs-highlight-color: #dee2e6;
|
||||
--bs-highlight-bg: #664d03;
|
||||
--bs-border-color: #495057;
|
||||
--bs-border-color-translucent: rgba(255, 255, 255, 0.15);
|
||||
--bs-form-valid-color: #75b798;
|
||||
--bs-form-valid-border-color: #75b798;
|
||||
--bs-form-invalid-color: #ea868f;
|
||||
--bs-form-invalid-border-color: #ea868f;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
:root {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: var(--bs-body-font-family);
|
||||
font-size: var(--bs-body-font-size);
|
||||
font-weight: var(--bs-body-font-weight);
|
||||
line-height: var(--bs-body-line-height);
|
||||
color: var(--bs-body-color);
|
||||
text-align: var(--bs-body-text-align);
|
||||
background-color: var(--bs-body-bg);
|
||||
-webkit-text-size-adjust: 100%;
|
||||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
hr {
|
||||
margin: 1rem 0;
|
||||
color: inherit;
|
||||
border: 0;
|
||||
border-top: var(--bs-border-width) solid;
|
||||
opacity: 0.25;
|
||||
}
|
||||
|
||||
h6, h5, h4, h3, h2, h1 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0.5rem;
|
||||
font-weight: 500;
|
||||
line-height: 1.2;
|
||||
color: var(--bs-heading-color);
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: calc(1.375rem + 1.5vw);
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
h1 {
|
||||
font-size: 2.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: calc(1.325rem + 0.9vw);
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
h2 {
|
||||
font-size: 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: calc(1.3rem + 0.6vw);
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
h3 {
|
||||
font-size: 1.75rem;
|
||||
}
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: calc(1.275rem + 0.3vw);
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
h4 {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
h5 {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
h6 {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
abbr[title] {
|
||||
-webkit-text-decoration: underline dotted;
|
||||
text-decoration: underline dotted;
|
||||
cursor: help;
|
||||
-webkit-text-decoration-skip-ink: none;
|
||||
text-decoration-skip-ink: none;
|
||||
}
|
||||
|
||||
address {
|
||||
margin-bottom: 1rem;
|
||||
font-style: normal;
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
ol,
|
||||
ul {
|
||||
padding-left: 2rem;
|
||||
}
|
||||
|
||||
ol,
|
||||
ul,
|
||||
dl {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
ol ol,
|
||||
ul ul,
|
||||
ol ul,
|
||||
ul ol {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
dt {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
dd {
|
||||
margin-bottom: 0.5rem;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
margin: 0 0 1rem;
|
||||
}
|
||||
|
||||
b,
|
||||
strong {
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
small {
|
||||
font-size: 0.875em;
|
||||
}
|
||||
|
||||
mark {
|
||||
padding: 0.1875em;
|
||||
color: var(--bs-highlight-color);
|
||||
background-color: var(--bs-highlight-bg);
|
||||
}
|
||||
|
||||
sub,
|
||||
sup {
|
||||
position: relative;
|
||||
font-size: 0.75em;
|
||||
line-height: 0;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
sub {
|
||||
bottom: -0.25em;
|
||||
}
|
||||
|
||||
sup {
|
||||
top: -0.5em;
|
||||
}
|
||||
|
||||
a {
|
||||
color: rgba(var(--bs-link-color-rgb), var(--bs-link-opacity, 1));
|
||||
text-decoration: underline;
|
||||
}
|
||||
a:hover {
|
||||
--bs-link-color-rgb: var(--bs-link-hover-color-rgb);
|
||||
}
|
||||
|
||||
a:not([href]):not([class]), a:not([href]):not([class]):hover {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
pre,
|
||||
code,
|
||||
kbd,
|
||||
samp {
|
||||
font-family: var(--bs-font-monospace);
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
pre {
|
||||
display: block;
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
overflow: auto;
|
||||
font-size: 0.875em;
|
||||
}
|
||||
pre code {
|
||||
font-size: inherit;
|
||||
color: inherit;
|
||||
word-break: normal;
|
||||
}
|
||||
|
||||
code {
|
||||
font-size: 0.875em;
|
||||
color: var(--bs-code-color);
|
||||
word-wrap: break-word;
|
||||
}
|
||||
a > code {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
kbd {
|
||||
padding: 0.1875rem 0.375rem;
|
||||
font-size: 0.875em;
|
||||
color: var(--bs-body-bg);
|
||||
background-color: var(--bs-body-color);
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
kbd kbd {
|
||||
padding: 0;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
figure {
|
||||
margin: 0 0 1rem;
|
||||
}
|
||||
|
||||
img,
|
||||
svg {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
table {
|
||||
caption-side: bottom;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
caption {
|
||||
padding-top: 0.5rem;
|
||||
padding-bottom: 0.5rem;
|
||||
color: var(--bs-secondary-color);
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
th {
|
||||
text-align: inherit;
|
||||
text-align: -webkit-match-parent;
|
||||
}
|
||||
|
||||
thead,
|
||||
tbody,
|
||||
tfoot,
|
||||
tr,
|
||||
td,
|
||||
th {
|
||||
border-color: inherit;
|
||||
border-style: solid;
|
||||
border-width: 0;
|
||||
}
|
||||
|
||||
label {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
button {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
button:focus:not(:focus-visible) {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
input,
|
||||
button,
|
||||
select,
|
||||
optgroup,
|
||||
textarea {
|
||||
margin: 0;
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
button,
|
||||
select {
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
[role=button] {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
select {
|
||||
word-wrap: normal;
|
||||
}
|
||||
select:disabled {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
[list]:not([type=date]):not([type=datetime-local]):not([type=month]):not([type=week]):not([type=time])::-webkit-calendar-picker-indicator {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
button,
|
||||
[type=button],
|
||||
[type=reset],
|
||||
[type=submit] {
|
||||
-webkit-appearance: button;
|
||||
}
|
||||
button:not(:disabled),
|
||||
[type=button]:not(:disabled),
|
||||
[type=reset]:not(:disabled),
|
||||
[type=submit]:not(:disabled) {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
::-moz-focus-inner {
|
||||
padding: 0;
|
||||
border-style: none;
|
||||
}
|
||||
|
||||
textarea {
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
fieldset {
|
||||
min-width: 0;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
legend {
|
||||
float: left;
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
margin-bottom: 0.5rem;
|
||||
font-size: calc(1.275rem + 0.3vw);
|
||||
line-height: inherit;
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
legend {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
}
|
||||
legend + * {
|
||||
clear: left;
|
||||
}
|
||||
|
||||
::-webkit-datetime-edit-fields-wrapper,
|
||||
::-webkit-datetime-edit-text,
|
||||
::-webkit-datetime-edit-minute,
|
||||
::-webkit-datetime-edit-hour-field,
|
||||
::-webkit-datetime-edit-day-field,
|
||||
::-webkit-datetime-edit-month-field,
|
||||
::-webkit-datetime-edit-year-field {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
::-webkit-inner-spin-button {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
[type=search] {
|
||||
-webkit-appearance: textfield;
|
||||
outline-offset: -2px;
|
||||
}
|
||||
|
||||
/* rtl:raw:
|
||||
[type="tel"],
|
||||
[type="url"],
|
||||
[type="email"],
|
||||
[type="number"] {
|
||||
direction: ltr;
|
||||
}
|
||||
*/
|
||||
::-webkit-search-decoration {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
::-webkit-color-swatch-wrapper {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
::-webkit-file-upload-button {
|
||||
font: inherit;
|
||||
-webkit-appearance: button;
|
||||
}
|
||||
|
||||
::file-selector-button {
|
||||
font: inherit;
|
||||
-webkit-appearance: button;
|
||||
}
|
||||
|
||||
output {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
iframe {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
summary {
|
||||
display: list-item;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
progress {
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
[hidden] {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/*# sourceMappingURL=bootstrap-reboot.css.map */
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1,594 +0,0 @@
|
||||
/*!
|
||||
* Bootstrap Reboot v5.3.3 (https://getbootstrap.com/)
|
||||
* Copyright 2011-2024 The Bootstrap Authors
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
*/
|
||||
:root,
|
||||
[data-bs-theme=light] {
|
||||
--bs-blue: #0d6efd;
|
||||
--bs-indigo: #6610f2;
|
||||
--bs-purple: #6f42c1;
|
||||
--bs-pink: #d63384;
|
||||
--bs-red: #dc3545;
|
||||
--bs-orange: #fd7e14;
|
||||
--bs-yellow: #ffc107;
|
||||
--bs-green: #198754;
|
||||
--bs-teal: #20c997;
|
||||
--bs-cyan: #0dcaf0;
|
||||
--bs-black: #000;
|
||||
--bs-white: #fff;
|
||||
--bs-gray: #6c757d;
|
||||
--bs-gray-dark: #343a40;
|
||||
--bs-gray-100: #f8f9fa;
|
||||
--bs-gray-200: #e9ecef;
|
||||
--bs-gray-300: #dee2e6;
|
||||
--bs-gray-400: #ced4da;
|
||||
--bs-gray-500: #adb5bd;
|
||||
--bs-gray-600: #6c757d;
|
||||
--bs-gray-700: #495057;
|
||||
--bs-gray-800: #343a40;
|
||||
--bs-gray-900: #212529;
|
||||
--bs-primary: #0d6efd;
|
||||
--bs-secondary: #6c757d;
|
||||
--bs-success: #198754;
|
||||
--bs-info: #0dcaf0;
|
||||
--bs-warning: #ffc107;
|
||||
--bs-danger: #dc3545;
|
||||
--bs-light: #f8f9fa;
|
||||
--bs-dark: #212529;
|
||||
--bs-primary-rgb: 13, 110, 253;
|
||||
--bs-secondary-rgb: 108, 117, 125;
|
||||
--bs-success-rgb: 25, 135, 84;
|
||||
--bs-info-rgb: 13, 202, 240;
|
||||
--bs-warning-rgb: 255, 193, 7;
|
||||
--bs-danger-rgb: 220, 53, 69;
|
||||
--bs-light-rgb: 248, 249, 250;
|
||||
--bs-dark-rgb: 33, 37, 41;
|
||||
--bs-primary-text-emphasis: #052c65;
|
||||
--bs-secondary-text-emphasis: #2b2f32;
|
||||
--bs-success-text-emphasis: #0a3622;
|
||||
--bs-info-text-emphasis: #055160;
|
||||
--bs-warning-text-emphasis: #664d03;
|
||||
--bs-danger-text-emphasis: #58151c;
|
||||
--bs-light-text-emphasis: #495057;
|
||||
--bs-dark-text-emphasis: #495057;
|
||||
--bs-primary-bg-subtle: #cfe2ff;
|
||||
--bs-secondary-bg-subtle: #e2e3e5;
|
||||
--bs-success-bg-subtle: #d1e7dd;
|
||||
--bs-info-bg-subtle: #cff4fc;
|
||||
--bs-warning-bg-subtle: #fff3cd;
|
||||
--bs-danger-bg-subtle: #f8d7da;
|
||||
--bs-light-bg-subtle: #fcfcfd;
|
||||
--bs-dark-bg-subtle: #ced4da;
|
||||
--bs-primary-border-subtle: #9ec5fe;
|
||||
--bs-secondary-border-subtle: #c4c8cb;
|
||||
--bs-success-border-subtle: #a3cfbb;
|
||||
--bs-info-border-subtle: #9eeaf9;
|
||||
--bs-warning-border-subtle: #ffe69c;
|
||||
--bs-danger-border-subtle: #f1aeb5;
|
||||
--bs-light-border-subtle: #e9ecef;
|
||||
--bs-dark-border-subtle: #adb5bd;
|
||||
--bs-white-rgb: 255, 255, 255;
|
||||
--bs-black-rgb: 0, 0, 0;
|
||||
--bs-font-sans-serif: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", "Liberation Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
||||
--bs-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||
--bs-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));
|
||||
--bs-body-font-family: var(--bs-font-sans-serif);
|
||||
--bs-body-font-size: 1rem;
|
||||
--bs-body-font-weight: 400;
|
||||
--bs-body-line-height: 1.5;
|
||||
--bs-body-color: #212529;
|
||||
--bs-body-color-rgb: 33, 37, 41;
|
||||
--bs-body-bg: #fff;
|
||||
--bs-body-bg-rgb: 255, 255, 255;
|
||||
--bs-emphasis-color: #000;
|
||||
--bs-emphasis-color-rgb: 0, 0, 0;
|
||||
--bs-secondary-color: rgba(33, 37, 41, 0.75);
|
||||
--bs-secondary-color-rgb: 33, 37, 41;
|
||||
--bs-secondary-bg: #e9ecef;
|
||||
--bs-secondary-bg-rgb: 233, 236, 239;
|
||||
--bs-tertiary-color: rgba(33, 37, 41, 0.5);
|
||||
--bs-tertiary-color-rgb: 33, 37, 41;
|
||||
--bs-tertiary-bg: #f8f9fa;
|
||||
--bs-tertiary-bg-rgb: 248, 249, 250;
|
||||
--bs-heading-color: inherit;
|
||||
--bs-link-color: #0d6efd;
|
||||
--bs-link-color-rgb: 13, 110, 253;
|
||||
--bs-link-decoration: underline;
|
||||
--bs-link-hover-color: #0a58ca;
|
||||
--bs-link-hover-color-rgb: 10, 88, 202;
|
||||
--bs-code-color: #d63384;
|
||||
--bs-highlight-color: #212529;
|
||||
--bs-highlight-bg: #fff3cd;
|
||||
--bs-border-width: 1px;
|
||||
--bs-border-style: solid;
|
||||
--bs-border-color: #dee2e6;
|
||||
--bs-border-color-translucent: rgba(0, 0, 0, 0.175);
|
||||
--bs-border-radius: 0.375rem;
|
||||
--bs-border-radius-sm: 0.25rem;
|
||||
--bs-border-radius-lg: 0.5rem;
|
||||
--bs-border-radius-xl: 1rem;
|
||||
--bs-border-radius-xxl: 2rem;
|
||||
--bs-border-radius-2xl: var(--bs-border-radius-xxl);
|
||||
--bs-border-radius-pill: 50rem;
|
||||
--bs-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
|
||||
--bs-box-shadow-sm: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
|
||||
--bs-box-shadow-lg: 0 1rem 3rem rgba(0, 0, 0, 0.175);
|
||||
--bs-box-shadow-inset: inset 0 1px 2px rgba(0, 0, 0, 0.075);
|
||||
--bs-focus-ring-width: 0.25rem;
|
||||
--bs-focus-ring-opacity: 0.25;
|
||||
--bs-focus-ring-color: rgba(13, 110, 253, 0.25);
|
||||
--bs-form-valid-color: #198754;
|
||||
--bs-form-valid-border-color: #198754;
|
||||
--bs-form-invalid-color: #dc3545;
|
||||
--bs-form-invalid-border-color: #dc3545;
|
||||
}
|
||||
|
||||
[data-bs-theme=dark] {
|
||||
color-scheme: dark;
|
||||
--bs-body-color: #dee2e6;
|
||||
--bs-body-color-rgb: 222, 226, 230;
|
||||
--bs-body-bg: #212529;
|
||||
--bs-body-bg-rgb: 33, 37, 41;
|
||||
--bs-emphasis-color: #fff;
|
||||
--bs-emphasis-color-rgb: 255, 255, 255;
|
||||
--bs-secondary-color: rgba(222, 226, 230, 0.75);
|
||||
--bs-secondary-color-rgb: 222, 226, 230;
|
||||
--bs-secondary-bg: #343a40;
|
||||
--bs-secondary-bg-rgb: 52, 58, 64;
|
||||
--bs-tertiary-color: rgba(222, 226, 230, 0.5);
|
||||
--bs-tertiary-color-rgb: 222, 226, 230;
|
||||
--bs-tertiary-bg: #2b3035;
|
||||
--bs-tertiary-bg-rgb: 43, 48, 53;
|
||||
--bs-primary-text-emphasis: #6ea8fe;
|
||||
--bs-secondary-text-emphasis: #a7acb1;
|
||||
--bs-success-text-emphasis: #75b798;
|
||||
--bs-info-text-emphasis: #6edff6;
|
||||
--bs-warning-text-emphasis: #ffda6a;
|
||||
--bs-danger-text-emphasis: #ea868f;
|
||||
--bs-light-text-emphasis: #f8f9fa;
|
||||
--bs-dark-text-emphasis: #dee2e6;
|
||||
--bs-primary-bg-subtle: #031633;
|
||||
--bs-secondary-bg-subtle: #161719;
|
||||
--bs-success-bg-subtle: #051b11;
|
||||
--bs-info-bg-subtle: #032830;
|
||||
--bs-warning-bg-subtle: #332701;
|
||||
--bs-danger-bg-subtle: #2c0b0e;
|
||||
--bs-light-bg-subtle: #343a40;
|
||||
--bs-dark-bg-subtle: #1a1d20;
|
||||
--bs-primary-border-subtle: #084298;
|
||||
--bs-secondary-border-subtle: #41464b;
|
||||
--bs-success-border-subtle: #0f5132;
|
||||
--bs-info-border-subtle: #087990;
|
||||
--bs-warning-border-subtle: #997404;
|
||||
--bs-danger-border-subtle: #842029;
|
||||
--bs-light-border-subtle: #495057;
|
||||
--bs-dark-border-subtle: #343a40;
|
||||
--bs-heading-color: inherit;
|
||||
--bs-link-color: #6ea8fe;
|
||||
--bs-link-hover-color: #8bb9fe;
|
||||
--bs-link-color-rgb: 110, 168, 254;
|
||||
--bs-link-hover-color-rgb: 139, 185, 254;
|
||||
--bs-code-color: #e685b5;
|
||||
--bs-highlight-color: #dee2e6;
|
||||
--bs-highlight-bg: #664d03;
|
||||
--bs-border-color: #495057;
|
||||
--bs-border-color-translucent: rgba(255, 255, 255, 0.15);
|
||||
--bs-form-valid-color: #75b798;
|
||||
--bs-form-valid-border-color: #75b798;
|
||||
--bs-form-invalid-color: #ea868f;
|
||||
--bs-form-invalid-border-color: #ea868f;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
:root {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: var(--bs-body-font-family);
|
||||
font-size: var(--bs-body-font-size);
|
||||
font-weight: var(--bs-body-font-weight);
|
||||
line-height: var(--bs-body-line-height);
|
||||
color: var(--bs-body-color);
|
||||
text-align: var(--bs-body-text-align);
|
||||
background-color: var(--bs-body-bg);
|
||||
-webkit-text-size-adjust: 100%;
|
||||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
hr {
|
||||
margin: 1rem 0;
|
||||
color: inherit;
|
||||
border: 0;
|
||||
border-top: var(--bs-border-width) solid;
|
||||
opacity: 0.25;
|
||||
}
|
||||
|
||||
h6, h5, h4, h3, h2, h1 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0.5rem;
|
||||
font-weight: 500;
|
||||
line-height: 1.2;
|
||||
color: var(--bs-heading-color);
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: calc(1.375rem + 1.5vw);
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
h1 {
|
||||
font-size: 2.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: calc(1.325rem + 0.9vw);
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
h2 {
|
||||
font-size: 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: calc(1.3rem + 0.6vw);
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
h3 {
|
||||
font-size: 1.75rem;
|
||||
}
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: calc(1.275rem + 0.3vw);
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
h4 {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
h5 {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
h6 {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
abbr[title] {
|
||||
-webkit-text-decoration: underline dotted;
|
||||
text-decoration: underline dotted;
|
||||
cursor: help;
|
||||
-webkit-text-decoration-skip-ink: none;
|
||||
text-decoration-skip-ink: none;
|
||||
}
|
||||
|
||||
address {
|
||||
margin-bottom: 1rem;
|
||||
font-style: normal;
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
ol,
|
||||
ul {
|
||||
padding-right: 2rem;
|
||||
}
|
||||
|
||||
ol,
|
||||
ul,
|
||||
dl {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
ol ol,
|
||||
ul ul,
|
||||
ol ul,
|
||||
ul ol {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
dt {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
dd {
|
||||
margin-bottom: 0.5rem;
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
margin: 0 0 1rem;
|
||||
}
|
||||
|
||||
b,
|
||||
strong {
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
small {
|
||||
font-size: 0.875em;
|
||||
}
|
||||
|
||||
mark {
|
||||
padding: 0.1875em;
|
||||
color: var(--bs-highlight-color);
|
||||
background-color: var(--bs-highlight-bg);
|
||||
}
|
||||
|
||||
sub,
|
||||
sup {
|
||||
position: relative;
|
||||
font-size: 0.75em;
|
||||
line-height: 0;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
sub {
|
||||
bottom: -0.25em;
|
||||
}
|
||||
|
||||
sup {
|
||||
top: -0.5em;
|
||||
}
|
||||
|
||||
a {
|
||||
color: rgba(var(--bs-link-color-rgb), var(--bs-link-opacity, 1));
|
||||
text-decoration: underline;
|
||||
}
|
||||
a:hover {
|
||||
--bs-link-color-rgb: var(--bs-link-hover-color-rgb);
|
||||
}
|
||||
|
||||
a:not([href]):not([class]), a:not([href]):not([class]):hover {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
pre,
|
||||
code,
|
||||
kbd,
|
||||
samp {
|
||||
font-family: var(--bs-font-monospace);
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
pre {
|
||||
display: block;
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
overflow: auto;
|
||||
font-size: 0.875em;
|
||||
}
|
||||
pre code {
|
||||
font-size: inherit;
|
||||
color: inherit;
|
||||
word-break: normal;
|
||||
}
|
||||
|
||||
code {
|
||||
font-size: 0.875em;
|
||||
color: var(--bs-code-color);
|
||||
word-wrap: break-word;
|
||||
}
|
||||
a > code {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
kbd {
|
||||
padding: 0.1875rem 0.375rem;
|
||||
font-size: 0.875em;
|
||||
color: var(--bs-body-bg);
|
||||
background-color: var(--bs-body-color);
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
kbd kbd {
|
||||
padding: 0;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
figure {
|
||||
margin: 0 0 1rem;
|
||||
}
|
||||
|
||||
img,
|
||||
svg {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
table {
|
||||
caption-side: bottom;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
caption {
|
||||
padding-top: 0.5rem;
|
||||
padding-bottom: 0.5rem;
|
||||
color: var(--bs-secondary-color);
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
th {
|
||||
text-align: inherit;
|
||||
text-align: -webkit-match-parent;
|
||||
}
|
||||
|
||||
thead,
|
||||
tbody,
|
||||
tfoot,
|
||||
tr,
|
||||
td,
|
||||
th {
|
||||
border-color: inherit;
|
||||
border-style: solid;
|
||||
border-width: 0;
|
||||
}
|
||||
|
||||
label {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
button {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
button:focus:not(:focus-visible) {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
input,
|
||||
button,
|
||||
select,
|
||||
optgroup,
|
||||
textarea {
|
||||
margin: 0;
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
button,
|
||||
select {
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
[role=button] {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
select {
|
||||
word-wrap: normal;
|
||||
}
|
||||
select:disabled {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
[list]:not([type=date]):not([type=datetime-local]):not([type=month]):not([type=week]):not([type=time])::-webkit-calendar-picker-indicator {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
button,
|
||||
[type=button],
|
||||
[type=reset],
|
||||
[type=submit] {
|
||||
-webkit-appearance: button;
|
||||
}
|
||||
button:not(:disabled),
|
||||
[type=button]:not(:disabled),
|
||||
[type=reset]:not(:disabled),
|
||||
[type=submit]:not(:disabled) {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
::-moz-focus-inner {
|
||||
padding: 0;
|
||||
border-style: none;
|
||||
}
|
||||
|
||||
textarea {
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
fieldset {
|
||||
min-width: 0;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
legend {
|
||||
float: right;
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
margin-bottom: 0.5rem;
|
||||
font-size: calc(1.275rem + 0.3vw);
|
||||
line-height: inherit;
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
legend {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
}
|
||||
legend + * {
|
||||
clear: right;
|
||||
}
|
||||
|
||||
::-webkit-datetime-edit-fields-wrapper,
|
||||
::-webkit-datetime-edit-text,
|
||||
::-webkit-datetime-edit-minute,
|
||||
::-webkit-datetime-edit-hour-field,
|
||||
::-webkit-datetime-edit-day-field,
|
||||
::-webkit-datetime-edit-month-field,
|
||||
::-webkit-datetime-edit-year-field {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
::-webkit-inner-spin-button {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
[type=search] {
|
||||
-webkit-appearance: textfield;
|
||||
outline-offset: -2px;
|
||||
}
|
||||
|
||||
[type="tel"],
|
||||
[type="url"],
|
||||
[type="email"],
|
||||
[type="number"] {
|
||||
direction: ltr;
|
||||
}
|
||||
::-webkit-search-decoration {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
::-webkit-color-swatch-wrapper {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
::-webkit-file-upload-button {
|
||||
font: inherit;
|
||||
-webkit-appearance: button;
|
||||
}
|
||||
|
||||
::file-selector-button {
|
||||
font: inherit;
|
||||
-webkit-appearance: button;
|
||||
}
|
||||
|
||||
output {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
iframe {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
summary {
|
||||
display: list-item;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
progress {
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
[hidden] {
|
||||
display: none !important;
|
||||
}
|
||||
/*# sourceMappingURL=bootstrap-reboot.rtl.css.map */
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
12057
src/frontend/src/css/bootstrap-5.3.3-dist/css/bootstrap.css
vendored
12057
src/frontend/src/css/bootstrap-5.3.3-dist/css/bootstrap.css
vendored
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
3
src/frontend/src/css/bulma.min.css
vendored
3
src/frontend/src/css/bulma.min.css
vendored
File diff suppressed because one or more lines are too long
@ -1,13 +0,0 @@
|
||||
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,17 +0,0 @@
|
||||
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'));
|
||||
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();
|
@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3"><g fill="#61DAFB"><path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/><circle cx="420.9" cy="296.5" r="45.7"/><path d="M520.5 78.1z"/></g></svg>
|
Before Width: | Height: | Size: 2.6 KiB |
@ -1,50 +0,0 @@
|
||||
import React, { useState } from 'react';
|
||||
import '../Connections.scss';
|
||||
|
||||
const Connections = () => {
|
||||
const [webhooks, setWebhooks] = useState([]);
|
||||
const [websocketUrl, setWebsocketUrl] = useState('');
|
||||
|
||||
const addWebhook = (newWebhook) => {
|
||||
setWebhooks([...webhooks, newWebhook]);
|
||||
};
|
||||
|
||||
const handleWebhookSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
const newWebhook = {
|
||||
url: e.target.webhookUrl.value,
|
||||
groups: e.target.groups.value.split(','),
|
||||
};
|
||||
addWebhook(newWebhook);
|
||||
e.target.reset();
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2>Настройки</h2>
|
||||
|
||||
{/* WebSocket подключение */}
|
||||
<div className="section">
|
||||
<h5>Уведомления</h5>
|
||||
<form
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
setWebsocketUrl(e.target.websocketUrl.value);
|
||||
}}
|
||||
>
|
||||
<input type="checkbox" id='notification' name="websocketUrl" required />
|
||||
<label for="notification">Уведомления о начале работы</label>
|
||||
<hr/>
|
||||
<input type="checkbox" id='notification' name="websocketUrl" required />
|
||||
<label for="notification">Уведомления о готовности вычислений</label>
|
||||
<hr/>
|
||||
<button type="submit">Подключиться</button>
|
||||
</form>
|
||||
|
||||
{websocketUrl && <p>Установлены значения: {websocketUrl}</p>}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Connections;
|
@ -1,30 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
export const CtxMenu = ({contextMenu, handleContextMenuClick }) => {
|
||||
return (
|
||||
<>
|
||||
{contextMenu.visible && (
|
||||
<ul
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: `${contextMenu.y}px`,
|
||||
left: `${contextMenu.x}px`,
|
||||
backgroundColor: 'transparent',
|
||||
listStyle: 'none',
|
||||
padding: '2px',
|
||||
boxShadow: '0px 0px 5px rgba(0,0,0,0.3)',
|
||||
zIndex: 1000,
|
||||
}}
|
||||
>
|
||||
<div className='context'>
|
||||
<div class="dot"></div>
|
||||
<li className={"btn"} onClick={() => handleContextMenuClick('add')}>Добавить объект</li>
|
||||
<li className={"btn"} onClick={() => handleContextMenuClick('add_base')}>Добавить базовую станцию</li>
|
||||
<li className={"btn"} onClick={() => handleContextMenuClick('save')}>Сохранить промежуточный результат</li>
|
||||
<li className={"btn"} onClick={() => handleContextMenuClick('cancel')}>Отмена</li>
|
||||
</div>
|
||||
</ul>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
@ -1,29 +0,0 @@
|
||||
|
||||
|
||||
.background_color {
|
||||
background: var(--bs-border-color);
|
||||
}
|
||||
.coler-border{
|
||||
border: 5px solid #2c3e50;
|
||||
background: #5b7996;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.context {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-flow: column;
|
||||
}
|
||||
.context li {
|
||||
color: aliceblue;
|
||||
}
|
||||
.context .dot {
|
||||
width: 10px; /* Ширина точки */
|
||||
height: 10px; /* Высота точки */
|
||||
background-color: blue; /* Синий цвет */
|
||||
border-radius: 50%; /* Скругляем углы до круга */
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.context .btn {
|
||||
background-color: rgba(91, 100, 185, 0.34);
|
||||
}
|
@ -1,519 +0,0 @@
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import * as THREE from 'three';
|
||||
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
|
||||
import Drone from './model/Drone';
|
||||
import BaseStation from './model/BaseStation';
|
||||
import HeightPoint from './model/HeightPoint';
|
||||
import { getCustomCookie } from '../Services/Local';
|
||||
import useWebsocketConnection, { DeleteSignal, spawnJsonSignal, RunSimulatorSignal } from '../Services/WebsocketHook';
|
||||
import './Dashboard.css';
|
||||
import { ModalDelete } from './Modal/MDelete';
|
||||
import { ModalAdd } from './Modal/MAdd';
|
||||
import { ModalSearch } from './Modal/MSearch';
|
||||
import { ModalJson } from './Modal/MJson';
|
||||
import { ModalMap } from './Modal/MMap';
|
||||
import { CtxMenu } from './ContextMenu/CtxMenu';
|
||||
import { ProgramInterface } from './MainScreen/ProgramInterface';
|
||||
import { SimulatorInterface } from './SimScreen/SimScreen';
|
||||
|
||||
const Dashboard = () => {
|
||||
const mountRef = useRef(null); // Указатель монтирования
|
||||
const sceneRef = useRef(null); // Указатель на сцену
|
||||
const cameraRef = useRef(null); // Указатель на камеру
|
||||
const {websocketStruct, sendMessage} = useWebsocketConnection(getCustomCookie("userToken"), 1);
|
||||
const [heightData, setHeightData] = useState([new HeightPoint()]); // Высоты
|
||||
const [formData, setFormData] = useState({ x: '', y: '', height: '' }); // Форма для ввода данных карты
|
||||
const [mouseState, setMouseState] = useState({ x: 0, y: 0, z: 0 }); // Форма для ввода данных карты
|
||||
const [drones, setDrones] = useState([]) // Все дроны
|
||||
const [selectedDrone, setSelectedDrone] = useState(null); // Состояние для выбранного дрона
|
||||
const [baseStation, setBaseStation] = useState([]) // Все базовые станции
|
||||
const [selectedBaseStation, setSelectedBaseStation] = useState(null); // Состояние для выбранной базовой станции
|
||||
const [mapSettings, setMapSettings] = useState({maxHeight: 20, coordX: 0, coordY: 0, }); // Настройки карты
|
||||
const [contextMenu, setContextMenu] = useState({ visible: false, x: 0, y: 0 }); // контекстное меню сцены
|
||||
const [objectType, setObjectType] = useState(1); // 1 = Drone, 2 = Base Station
|
||||
const [formObjectData, setFormObjectData] = useState({
|
||||
name: "",
|
||||
type: 0, // 1 = Drone, 2 = Base Station
|
||||
coordinates_x: 0, // Координата X на карте
|
||||
coordinates_y: 0, // Координата Y на карте
|
||||
coordinates_z: 0, // Координата Z на карте
|
||||
|
||||
antennaRadius: 0, // Радиус антенны
|
||||
antennaDirection_x: 0,// Направление антенны по X
|
||||
antennaDirection_y: 0,// Направление антенны по Y
|
||||
antennaDirection_z: 0,// Направление антенны по Z
|
||||
|
||||
modulation: "", // Модуляция
|
||||
bandwidth: 0, // Пропускная способность
|
||||
dataRate: 0, // Скорость передачи данных
|
||||
// DRONE FIELDS
|
||||
wayPoints: 0, // Точки следования
|
||||
speed: 0, // Скорость
|
||||
meshName: "", // Название сети
|
||||
});
|
||||
const [searchTerm, setSearchTerm] = useState("");
|
||||
const [isSimulationRunning, setIsSimulationRunning] = useState(false);
|
||||
|
||||
// Рекурсивная функция для поиска объекта среди детей
|
||||
const containsObjectRecursively = (parent, target) => {
|
||||
if (parent === target) {
|
||||
return true;
|
||||
}
|
||||
for (let child of parent.children) {
|
||||
if (containsObjectRecursively(child, target)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
// Обработка websocket
|
||||
useEffect(() => {
|
||||
if (websocketStruct && websocketStruct.modulation) {
|
||||
const modulation = websocketStruct.modulation;
|
||||
// Парсинг карты
|
||||
const map = modulation.map;
|
||||
setMapSettings({
|
||||
name: map.name,
|
||||
minBound: map.map.MinBound,
|
||||
maxBound: map.map.MaxBound,
|
||||
heightData: map.map.HeightData,
|
||||
maxHeight: 20,
|
||||
});
|
||||
setHeightData(map.map.HeightData.flatMap((row, x) => row.map((height, y) => new HeightPoint(x, y, height))));
|
||||
|
||||
// Парсинг объектов
|
||||
const parsedDrones = [];
|
||||
const parsedBaseStations = [];
|
||||
modulation.objects.forEach((obj) => {
|
||||
if (obj.type === 1) {
|
||||
const drone = new Drone(obj.name);
|
||||
drone.setPosition(...obj.coords);
|
||||
parsedDrones.push(drone);
|
||||
} else if (obj.type === 2) {
|
||||
const base = new BaseStation(obj.name);
|
||||
base.setPosition(...obj.coords);
|
||||
parsedBaseStations.push(base);
|
||||
}
|
||||
});
|
||||
setDrones(parsedDrones);
|
||||
setBaseStation(parsedBaseStations);
|
||||
}
|
||||
}, [websocketStruct]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!mountRef.current) return;
|
||||
let width = mountRef.current.clientWidth;
|
||||
let height = mountRef.current.clientHeight;
|
||||
// Создаем объект сцены
|
||||
const scene = new THREE.Scene();
|
||||
scene.background = new THREE.Color(0xffffff);
|
||||
// Сохраняем ссылку на сцену
|
||||
sceneRef.current = scene;
|
||||
// Создаем камеру
|
||||
const camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 2000);
|
||||
camera.position.set(0, 80, 120);
|
||||
cameraRef.current = camera;
|
||||
|
||||
const renderer = new THREE.WebGLRenderer();
|
||||
renderer.setSize(width, height);
|
||||
mountRef.current.appendChild(renderer.domElement);
|
||||
|
||||
// Плоскость для высотной карты :: TODO :: W/H/Ws/Hs change
|
||||
const geometry = new THREE.PlaneGeometry(200, 200, 20, 20);
|
||||
const vertices = geometry.attributes.position.array;
|
||||
const colors = [];
|
||||
const color = new THREE.Color();
|
||||
|
||||
const minHeight = Math.min(...heightData.map((point) => point.height));
|
||||
const maxHeight = Math.max(mapSettings.maxHeight, ...heightData.map((point) => point.height));
|
||||
|
||||
for (let i = 0; i < vertices.length; i += 3) {
|
||||
const x = Math.floor(i / 3) % 5;
|
||||
const y = Math.floor(i / (3 * 5));
|
||||
|
||||
const heightPoint = heightData.find((point) => point.x === x && point.y === y);
|
||||
const heightValue = heightPoint ? heightPoint.height : 0;
|
||||
|
||||
vertices[i + 2] = heightValue;
|
||||
|
||||
const normalizedHeight = (heightValue - minHeight) / (maxHeight - minHeight);
|
||||
color.setHSL(0.7 * (1 - normalizedHeight), 1, 0.5);
|
||||
colors.push(color.r, color.g, color.b);
|
||||
}
|
||||
|
||||
geometry.setAttribute('color', new THREE.Float32BufferAttribute(colors, 3));
|
||||
|
||||
const material = new THREE.MeshBasicMaterial({
|
||||
vertexColors: true,
|
||||
side: THREE.DoubleSide,
|
||||
wireframe: false,
|
||||
});
|
||||
|
||||
const mesh = new THREE.Mesh(geometry, material);
|
||||
mesh.rotation.x = -Math.PI / 2;
|
||||
scene.add(mesh);
|
||||
|
||||
const axesHelper = new THREE.AxesHelper(100);
|
||||
scene.add(axesHelper);
|
||||
|
||||
const controls = new OrbitControls(camera, renderer.domElement);
|
||||
controls.enableDamping = true;
|
||||
|
||||
const light = new THREE.DirectionalLight(0xffffff, 1);
|
||||
light.position.set(0, 50, 50).normalize();
|
||||
scene.add(light);
|
||||
// Функция для обновления размеров при изменении размеров окна
|
||||
const handleResize = () => {
|
||||
const newWidth = mountRef.current.clientWidth;
|
||||
const newHeight = mountRef.current.clientHeight;
|
||||
camera.aspect = newWidth / newHeight;
|
||||
camera.updateProjectionMatrix();
|
||||
renderer.setSize(newWidth, newHeight);
|
||||
};
|
||||
|
||||
// Добавляем обработчик события resize
|
||||
const handleMouseMove = (event) => {
|
||||
let x = event.x;
|
||||
let y = event.y;
|
||||
setMouseState({ x: x, y: y, z: 0 });
|
||||
}
|
||||
const animate = () => {
|
||||
requestAnimationFrame(animate);
|
||||
controls.update();
|
||||
renderer.render(scene, camera);
|
||||
};
|
||||
window.addEventListener('resize', handleResize);
|
||||
renderer.domElement.addEventListener('click', handleClick);
|
||||
renderer.domElement.addEventListener('mousemove', handleMouseMove);
|
||||
|
||||
animate();
|
||||
drones.forEach(droneData => {
|
||||
scene.add(droneData.getObject());
|
||||
});
|
||||
baseStation.forEach(b => {
|
||||
scene.add(b.getObject());
|
||||
});
|
||||
|
||||
// Обработка нажатия ПКМ
|
||||
renderer.domElement.addEventListener('contextmenu', (event) => {
|
||||
event.preventDefault();
|
||||
setContextMenu({
|
||||
visible: true,
|
||||
x: event.clientX,
|
||||
y: event.clientY,
|
||||
});
|
||||
});
|
||||
return () => {
|
||||
if (mountRef.current) {
|
||||
|
||||
window.removeEventListener('resize', handleResize)
|
||||
mountRef.current.removeChild(renderer.domElement);
|
||||
}
|
||||
};
|
||||
}, [heightData, mapSettings]);
|
||||
|
||||
// Обработка кликов на сцене
|
||||
const handleClick = (event) => {
|
||||
// Рассчитываем координаты мыши в нормализованной системе координат и округляем до целого числа
|
||||
const mouse = new THREE.Vector2();
|
||||
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
|
||||
mouse.y = - (event.clientY / window.innerHeight) * 2 + 1;
|
||||
console.log('Mouse coordinates:', mouse);
|
||||
|
||||
// Создаем объект Raycaster для проверки пересечений
|
||||
const raycaster = new THREE.Raycaster();
|
||||
const rayHelper = new THREE.ArrowHelper(raycaster.ray.direction, raycaster.ray.origin, 100, 0xff4444);
|
||||
sceneRef.current.add(rayHelper);
|
||||
raycaster.setFromCamera(mouse, cameraRef.current);
|
||||
|
||||
// Проверяем пересечения с объектами дронов
|
||||
const intersects = raycaster.intersectObjects(drones.map(drone => drone.getObject()), true);
|
||||
console.log('Intersections:', intersects);
|
||||
console.log('Drones:', drones.map(drone => [drone.getObject(), drone.getObject().position, drone.name]));
|
||||
|
||||
if (intersects.length > 0) {
|
||||
const selected = intersects[0].object;
|
||||
console.log('Clicked on:', selected); // Лог для проверки объекта
|
||||
|
||||
// Поиск дрона рекурсивно
|
||||
const drone = drones.find(drone => containsObjectRecursively(drone.getObject(), selected));
|
||||
|
||||
if (drone) {
|
||||
// Сбрасываем цвет предыдущего выбранного дрона
|
||||
if (selectedDrone) {
|
||||
drone.getObject().visible = true;
|
||||
}
|
||||
// Устанавливаем цвет выбранного дрона
|
||||
drone.getObject().visible = false;
|
||||
console.log(drone);
|
||||
setSelectedDrone(drone);
|
||||
}
|
||||
} else {
|
||||
// Сбрасываем выбор, если ни один дрон не был выбран
|
||||
if (selectedDrone) {
|
||||
selectedDrone.getObject().visible = true;
|
||||
setSelectedDrone(null);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Остальной код компонента остается неизменным
|
||||
|
||||
|
||||
const handleSearch = () => {
|
||||
const foundDrone = drones.find(drone => drone.name.toLowerCase() === searchTerm.toLowerCase());
|
||||
const foundBaseStation = baseStation.find(base => base.name.toLowerCase() === searchTerm.toLowerCase());
|
||||
|
||||
if (foundDrone) {
|
||||
// Выделяем найденный дрон
|
||||
if (selectedDrone) {
|
||||
selectedDrone.getObject().traverse((child) => {
|
||||
if (child.isMesh) {
|
||||
child.material.color.set(0xff0000);
|
||||
}
|
||||
});
|
||||
}
|
||||
foundDrone.getObject().traverse((child) => {
|
||||
if (child.isMesh) {
|
||||
child.material.color.set(0x00ff00);
|
||||
}
|
||||
});
|
||||
setSelectedDrone(foundDrone);
|
||||
}
|
||||
if (foundBaseStation) {
|
||||
// Выделяем найденную базу
|
||||
if (selectedBaseStation) {
|
||||
selectedBaseStation.getObject().traverse((child) => {
|
||||
if (child.isMesh) {
|
||||
child.material.color.set(0x0000ff);
|
||||
}
|
||||
});
|
||||
}
|
||||
foundBaseStation.getObject().traverse((child) => {
|
||||
if (child.isMesh) {
|
||||
child.material.color.set(0x00ff00);
|
||||
}
|
||||
});
|
||||
setSelectedBaseStation(foundBaseStation);
|
||||
} else {
|
||||
alert("Объект с указанным именем не найден");
|
||||
}
|
||||
};
|
||||
const handleTypeChange = (type) => {
|
||||
setObjectType(type);
|
||||
setFormData({ ...formData, type });
|
||||
};
|
||||
const handleDeleteSignal = (name) => {
|
||||
sendMessage(DeleteSignal(name));
|
||||
};
|
||||
// Одно состояние для управления всеми модальными окнами
|
||||
const [modals, setModals] = useState({
|
||||
isModalOpen: false,
|
||||
isModalAddOpen: false,
|
||||
isModalDeleteOpen: false,
|
||||
isModalSearchOpen: false,
|
||||
isModalMapOpen: false,
|
||||
});
|
||||
// Функция открытия модального окна в зависимости от переданной строки
|
||||
const openModal = (modalName) => {
|
||||
setModals((prevModals) => ({
|
||||
...prevModals,
|
||||
[modalName]: true,
|
||||
}));
|
||||
};
|
||||
// Функция закрытия всех модальных окон
|
||||
const closeAllModals = () => {
|
||||
setModals({
|
||||
isModalOpen: false,
|
||||
isModalAddOpen: false,
|
||||
isModalDeleteOpen: false,
|
||||
isModalSearchOpen: false,
|
||||
});
|
||||
};
|
||||
|
||||
// Добавление дрона в сцену
|
||||
const handleAddDrone = () => {
|
||||
if (sceneRef.current) {
|
||||
const current = Date.now();
|
||||
const drone = new Drone(current / 1000);
|
||||
// Создаем новый материал для каждого дрона
|
||||
drone.setPosition(Math.random() * 20 - 10, 20, Math.random() * 20 - 10);
|
||||
setDrones((prevDrones) => [...prevDrones, drone]);
|
||||
console.log(drones.map(drone => drone.getObject()));
|
||||
let json_signal = spawnJsonSignal(1, current / 1000 + "", "" + Math.round(drone.getObject().position.x) + "," + Math.round(drone.getObject().position.y) + "," + Math.round(drone.getObject().position.z) + "");
|
||||
sendMessage(json_signal)
|
||||
sceneRef.current.add(drone.getObject());
|
||||
}
|
||||
};
|
||||
const modalAddDroneOrBaseStation = () => {
|
||||
//formObjectData
|
||||
alert(`Добавлено: ${JSON.stringify(formObjectData)}`);
|
||||
if (sceneRef.current) {
|
||||
if (objectType === 1) {
|
||||
const drone = new Drone(formObjectData.name);
|
||||
drone.setPosition(formObjectData.coordinates_x, formObjectData.coordinates_y, formObjectData.coordinates_z);
|
||||
setDrones((prevDrones) => [...prevDrones, drone]);
|
||||
let json_signal = spawnJsonSignal(
|
||||
1,
|
||||
formObjectData.name,
|
||||
`${formObjectData.coordinates_x},${formObjectData.coordinates_y},${formObjectData.coordinates_z}`,
|
||||
formObjectData.antennaRadius,
|
||||
`${formObjectData.antennaDirection_x},${formObjectData.antennaDirection_y},${formObjectData.antennaDirection_z}`,
|
||||
formObjectData.modulation,
|
||||
formObjectData.bandwidth,
|
||||
formObjectData.dataRate,
|
||||
formObjectData.wayPoints,
|
||||
formObjectData.speed,
|
||||
formObjectData.meshName
|
||||
);
|
||||
sendMessage(json_signal)
|
||||
sceneRef.current.add(drone.getObject());
|
||||
}
|
||||
}
|
||||
}
|
||||
// Добавление базовой станции в сцену
|
||||
const handleAddBaseStation = () => {
|
||||
if (sceneRef.current) {
|
||||
const current = Date.now();
|
||||
const base = new BaseStation(current / 1000);
|
||||
|
||||
// Выбираем случайные координаты X и Z
|
||||
const x = Math.random() * 20 - 10;
|
||||
const z = Math.random() * 20 - 10;
|
||||
|
||||
// Находим точку высоты для установки базовой станции на карту
|
||||
const heightPoint = heightData.find(point => point.x === Math.round(x) && point.y === Math.round(z));
|
||||
console.log(heightPoint)
|
||||
const y = heightPoint ? heightPoint.height : 0;
|
||||
|
||||
// Устанавливаем позицию базовой станции на основании высоты карты
|
||||
base.setPosition(x, y, z);
|
||||
|
||||
setBaseStation((prev) => [...prev, base]);
|
||||
console.log(baseStation.map(b => b.getObject().children[0]));
|
||||
sceneRef.current.add(base.getObject());
|
||||
}
|
||||
};
|
||||
// Обработчик ввода на форму высот
|
||||
const handleInputChange = (event) => {
|
||||
const { name, value } = event.target;
|
||||
setFormData({
|
||||
...formData,
|
||||
[name]: value,
|
||||
});
|
||||
};
|
||||
// Обработчик ввода на форму объектов
|
||||
const handleAddInputChange = (event) => {
|
||||
const { name, value } = event.target;
|
||||
setFormObjectData({
|
||||
...formData,
|
||||
[name]: value,
|
||||
});
|
||||
};
|
||||
// Добавление элемента карты высот (высоты)
|
||||
const handleAddHeight = (event) => {
|
||||
event.preventDefault();
|
||||
const { x, y, height } = formData;
|
||||
const newPoint = new HeightPoint(parseInt(x), parseInt(y), parseFloat(height));
|
||||
setHeightData((prevData) => [...prevData, newPoint]);
|
||||
setFormData({ x: '', y: '', height: '' });
|
||||
};
|
||||
// Изменение настроек
|
||||
const handleSettingsChange = (event) => {
|
||||
const { name, value } = event.target;
|
||||
setMapSettings({
|
||||
...mapSettings,
|
||||
[name]: value,
|
||||
});
|
||||
};
|
||||
// Открытие контекстного меню
|
||||
const handleContextMenuClick = (action) => {
|
||||
if (action === 'add_base'){ handleAddBaseStation();}
|
||||
else if (action === 'add') { handleAddDrone(); } else if (action === 'save') {
|
||||
console.log('Сохранить промежуточный результат');
|
||||
}
|
||||
setContextMenu({ ...contextMenu, visible: false });
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{ flex: 1 }}>
|
||||
<div className='col'>
|
||||
<h4><b>Drone</b> Network Simulator - окно разработки</h4>
|
||||
</div>
|
||||
<ModalAdd
|
||||
modals={modals.isModalAddOpen}
|
||||
closeAllModals={closeAllModals}
|
||||
handleAddInputChange={handleAddInputChange}
|
||||
handleTypeChange={handleTypeChange}
|
||||
objectType={objectType}
|
||||
formObjectData={formObjectData}
|
||||
modalAddDroneOrBaseStation={modalAddDroneOrBaseStation}
|
||||
/>
|
||||
|
||||
|
||||
<ModalSearch
|
||||
isModalSearchOpen={modals.isModalSearchOpen}
|
||||
closeAllModals={closeAllModals}
|
||||
handleSearch={handleSearch}
|
||||
searchTerm={searchTerm}
|
||||
setSearchTerm={setSearchTerm}
|
||||
/>
|
||||
|
||||
<ModalDelete
|
||||
isModalDeleteOpen={modals.isModalDeleteOpen}
|
||||
closeAllModals={closeAllModals}
|
||||
handleDeleteSignal={handleDeleteSignal}
|
||||
/>
|
||||
|
||||
<ModalJson
|
||||
isModalOpen={modals.isModalOpen}
|
||||
closeAllModals={closeAllModals}
|
||||
/>
|
||||
|
||||
<ModalMap isModalMapOpen={modals.isModalMapOpen}
|
||||
closeAllModals={closeAllModals}
|
||||
handleAddHeight={handleAddHeight}
|
||||
formData={formData}
|
||||
handleInputChange={handleInputChange}
|
||||
mapSettings={mapSettings}
|
||||
handleSettingsChange={handleSettingsChange}/>
|
||||
<ul className="nav">
|
||||
<li className="nav-item">
|
||||
<a className="nav-link active"onClick={() => setIsSimulationRunning(true)} >Активная</a>
|
||||
</li>
|
||||
<li className="nav-item">
|
||||
<a className="nav-link" onClick={() => setIsSimulationRunning(true)}>Симуляция</a>
|
||||
</li>
|
||||
</ul>
|
||||
{/* Окно программы */}
|
||||
{isSimulationRunning ? (
|
||||
// isSimulationRunning
|
||||
<SimulatorInterface
|
||||
mouseState={mouseState}
|
||||
mountRef={mountRef}
|
||||
openModal={openModal}
|
||||
sendMessage={sendMessage}
|
||||
RunSimulatorSignal={RunSimulatorSignal}
|
||||
getCustomCookie={getCustomCookie}
|
||||
/>
|
||||
) : (
|
||||
<ProgramInterface
|
||||
mouseState={mouseState}
|
||||
mountRef={mountRef}
|
||||
openModal={openModal}
|
||||
sendMessage={sendMessage}
|
||||
RunSimulatorSignal={RunSimulatorSignal}
|
||||
getCustomCookie={getCustomCookie}
|
||||
/>
|
||||
)}
|
||||
<CtxMenu
|
||||
contextMenu={contextMenu}
|
||||
handleContextMenuClick={handleContextMenuClick}
|
||||
/>
|
||||
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Dashboard;
|
@ -1,93 +0,0 @@
|
||||
import React, { useState } from 'react';
|
||||
import '../css/DeviceGroups.css';
|
||||
|
||||
const DeviceGroups = () => {
|
||||
const [groups, setGroups] = useState([]);
|
||||
const [newGroupName, setNewGroupName] = useState('');
|
||||
|
||||
const createGroup = () => {
|
||||
if (newGroupName) {
|
||||
const newGroup = {
|
||||
name: newGroupName,
|
||||
devices: [],
|
||||
active: true,
|
||||
};
|
||||
setGroups([...groups, newGroup]);
|
||||
setNewGroupName('');
|
||||
}
|
||||
};
|
||||
|
||||
const deleteGroup = (groupName) => {
|
||||
setGroups(groups.filter(group => group.name !== groupName));
|
||||
};
|
||||
|
||||
const toggleGroupStatus = (groupName) => {
|
||||
setGroups(groups.map(group =>
|
||||
group.name === groupName ? { ...group, active: !group.active } : group
|
||||
));
|
||||
};
|
||||
|
||||
const addDeviceToGroup = (groupName, device) => {
|
||||
setGroups(groups.map(group =>
|
||||
group.name === groupName ? { ...group, devices: [...group.devices, device] } : group
|
||||
));
|
||||
};
|
||||
|
||||
const removeDeviceFromGroup = (groupName, device) => {
|
||||
setGroups(groups.map(group =>
|
||||
group.name === groupName ? { ...group, devices: group.devices.filter(d => d !== device) } : group
|
||||
));
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="device-groups">
|
||||
<h2>Шаблоны / СШК</h2>
|
||||
|
||||
<div className="group-creation">
|
||||
<input
|
||||
type="text"
|
||||
value={newGroupName}
|
||||
onChange={(e) => setNewGroupName(e.target.value)}
|
||||
placeholder="Название шаблона"
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
value={newGroupName}
|
||||
onChange={(e) => setNewGroupName(e.target.value)}
|
||||
placeholder="JSON Template"
|
||||
/>
|
||||
<button className='btn btn-success' onClick={createGroup}>Создать шаблон</button>
|
||||
</div>
|
||||
|
||||
<ul>
|
||||
{groups.map(group => (
|
||||
<li key={group.name} className={group.active ? 'active' : 'inactive'}>
|
||||
<div>
|
||||
<span>{group.name}</span>
|
||||
<button onClick={() => deleteGroup(group.name)}>Удалить</button>
|
||||
<button onClick={() => toggleGroupStatus(group.name)}>
|
||||
{group.active ? 'Деактивировать' : 'Активировать'}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h4>Устройства в группе:</h4>
|
||||
<ul>
|
||||
{group.devices.map((device, index) => (
|
||||
<li key={index}>
|
||||
{device} <button onClick={() => removeDeviceFromGroup(group.name, device)}>Удалить</button>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<button onClick={() => addDeviceToGroup(group.name, 'Новое устройство')}>
|
||||
Добавить устройство
|
||||
</button>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default DeviceGroups;
|
@ -1,24 +0,0 @@
|
||||
import React, { useState } from 'react';
|
||||
import { QRCodeCanvas } from "qrcode.react";
|
||||
import '../css/Devices.css';
|
||||
|
||||
const Devices = () => {
|
||||
return (
|
||||
<div>
|
||||
<p>Разработчик: @moxitech</p>
|
||||
<img style={{ width: 200, height: 200 }} src='/recources/moxitech.webp'></img>
|
||||
<hr/>
|
||||
<button className='button is-danger'>Удалить неиспользуемые шаблоны</button>
|
||||
<hr/>
|
||||
<button className='button is-danger'>Удалить неактивных пользователей</button>
|
||||
<hr/>
|
||||
<button className='button is-danger'>Перезагрузить сервер</button>
|
||||
<hr/>
|
||||
|
||||
<button className='button is-danger'>Перезагрузить фронтенд</button>
|
||||
|
||||
</div>
|
||||
)
|
||||
};
|
||||
|
||||
export default Devices;
|
@ -1,19 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
const Docs = () => {
|
||||
return (
|
||||
<div className="block">
|
||||
<div className="block">
|
||||
Документация <strong>Drone Network Simulator</strong>.
|
||||
</div>
|
||||
<div className="block">
|
||||
created by <strong>@moxitech</strong>
|
||||
</div>
|
||||
<div className="block">
|
||||
Для начала работы необходимо войти в систему
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Docs;
|
@ -1,84 +0,0 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { setCustomCookie, getCustomCookie } from '../Services/Local'
|
||||
import { giveMeServerAddress } from '../Services/Server'
|
||||
|
||||
import '../Login.scss';
|
||||
|
||||
const Login = ({ onLogin }) => {
|
||||
const [username, setUsername] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
const [error, setError] = useState('');
|
||||
useEffect(() => {
|
||||
let cook = getCustomCookie('userToken')
|
||||
if (cook) {
|
||||
onLogin();
|
||||
}
|
||||
}, [])
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
try {
|
||||
// Отправляем запрос на сервер
|
||||
const response = await fetch(giveMeServerAddress() + "/auth/"+ username +"/" + password, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ username, password })
|
||||
});
|
||||
|
||||
// Проверяем статус ответа
|
||||
if (!response.ok) {
|
||||
// Если статус не 200, ничего не делаем
|
||||
setError('Ошибка авторизации. Пожалуйста, попробуйте снова.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Парсим тело ответа в формате JSON
|
||||
const data = await response.json();
|
||||
|
||||
// Устанавливаем userToken из тела ответа
|
||||
setCustomCookie('userToken', data.userid, { expires: 7 });
|
||||
|
||||
// Вызываем функцию после успешного входа в систему
|
||||
onLogin();
|
||||
} catch (error) {
|
||||
// Обработка ошибок сети
|
||||
setError('Произошла ошибка. Пожалуйста, попробуйте снова позже.');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="login-container">
|
||||
<div className="login-box"> {/* Added a wrapper for styling */}
|
||||
<h2>Вход</h2>
|
||||
{error && <p className="error">{error}</p>}
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className="input-group">
|
||||
<label htmlFor="username">Логин</label>
|
||||
<input
|
||||
id="username"
|
||||
type="text"
|
||||
placeholder="Логин"
|
||||
value={username}
|
||||
onChange={(e) => setUsername(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="input-group">
|
||||
<label htmlFor="password">Пароль</label>
|
||||
<input
|
||||
id="password"
|
||||
type="password"
|
||||
placeholder="Пароль"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<button type="submit">Войти</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Login;
|
@ -1,78 +0,0 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import '../css/Custom/Main.css';
|
||||
import { ServerRequest, giveMeServerWebsocketRequestAddress } from '../Services/Server';
|
||||
import { setCustomCookie, getCustomCookie } from '../Services/Local'
|
||||
const Main = () => {
|
||||
const [rooms, setRooms] = useState(null);
|
||||
const [isCreating, setIsCreating] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
getSimulations();
|
||||
|
||||
}, []);
|
||||
|
||||
const createSimulation = () => {
|
||||
setIsCreating(true);
|
||||
giveMeServerWebsocketRequestAddress();
|
||||
// TODO: Make POST request to create simulation here.
|
||||
// POST $BASE_ADDR/createSimulation?userToken=?
|
||||
|
||||
// Simulating an async request for demo purposes
|
||||
setTimeout(() => {
|
||||
setIsCreating(false);
|
||||
}, 2000);
|
||||
};
|
||||
|
||||
const getSimulations = () => {
|
||||
ServerRequest("/simulations/active", "GET").then(x => {
|
||||
console.log(x.data, x.status);
|
||||
if (x.status === 200) {
|
||||
setRooms(x.data.rooms_ids);
|
||||
} else {
|
||||
setRooms(null);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className='main__imager' style={{ marginTop: "2%" }}>
|
||||
<div className="row align-items-start">
|
||||
<div className="col">
|
||||
<h1 className="title">Создать новую симуляцию</h1>
|
||||
<hr />
|
||||
|
||||
<button
|
||||
className="btn btn-primary"
|
||||
onClick={createSimulation}
|
||||
disabled={isCreating}
|
||||
>
|
||||
{isCreating && <div className="spinner-border text-light me-2" role="status" />}
|
||||
Create
|
||||
</button>
|
||||
</div>
|
||||
<div className="col">
|
||||
<h1 className="title">Присоединиться к моделированию</h1>
|
||||
<hr />
|
||||
{rooms && rooms.length > 0 ? (
|
||||
rooms.map(room => (
|
||||
<div className='row' key={room.uuid}>
|
||||
<div className='col'>
|
||||
<h3>{room.name} | UUID : {room.uuid}</h3>
|
||||
</div>
|
||||
<div className='col'>
|
||||
<button className="btn btn-primary">
|
||||
Connect
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<p>Пока никто не работает 😒</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Main;
|
@ -1,91 +0,0 @@
|
||||
|
||||
|
||||
|
||||
export const ProgramInterface = ({mouseState, mountRef, openModal, sendMessage, RunSimulatorSignal, getCustomCookie}) => {
|
||||
const handleFullScreen = () => {
|
||||
if (mountRef.current) {
|
||||
if (mountRef.current.requestFullscreen) {
|
||||
mountRef.current.requestFullscreen();
|
||||
} else if (mountRef.current.webkitRequestFullscreen) { // для Safari
|
||||
mountRef.current.webkitRequestFullscreen();
|
||||
} else if (mountRef.current.msRequestFullscreen) { // для IE/Edge
|
||||
mountRef.current.msRequestFullscreen();
|
||||
}
|
||||
}
|
||||
};
|
||||
return (
|
||||
<div className='coler-border'>
|
||||
<div>
|
||||
<p> X: {mouseState.x} Y: {mouseState.y} Z: {mouseState.z}</p>
|
||||
</div>
|
||||
<div ref={mountRef} style={{
|
||||
minWidth: '100%',
|
||||
minHeight: '60vh',
|
||||
position: 'relative'
|
||||
}} />
|
||||
<div class="btn-toolbar" role="toolbar" aria-label="Toolbar with button groups">
|
||||
<div class="btn-group me-2" role="group" aria-label="First group">
|
||||
<button type="button" onClick={handleFullScreen}>
|
||||
<i class="bi-fullscreen"/>Развернуть на весь экран
|
||||
</button>
|
||||
<button type="button" onClick={() => openModal('isModalAddOpen')} class="btn btn-success">
|
||||
<i class="bi-plus"/>Добавить
|
||||
</button>
|
||||
|
||||
<button type="button" onClick={() => openModal('isModalDeleteOpen')} class="btn btn-success">
|
||||
<i class="bi-trash"/>Удалить
|
||||
</button>
|
||||
|
||||
<button type="button" onClick={() => openModal('isModalSearchOpen')} class="btn btn-success">
|
||||
<i class="bi-search"/>Выбрать
|
||||
</button>
|
||||
|
||||
<button type="button" class="btn btn-success">
|
||||
<i class="bi-save"/>Сохранить
|
||||
</button>
|
||||
|
||||
</div>
|
||||
<div class="btn-group me-2" role="group" aria-label="Second group">
|
||||
|
||||
<button type="button" class="btn btn-secondary">
|
||||
<i class="bi-box-arrow-in-down"/>Загрузить данные
|
||||
</button>
|
||||
|
||||
<button type="button" class="btn btn-secondary">
|
||||
<i class="bi-filetype-json"/>Выгрузить
|
||||
</button>
|
||||
|
||||
<button type="button" onClick={() => openModal('isModalMapOpen')} class="btn btn-secondary">
|
||||
<i class="bi-map"/>Настройки карты
|
||||
</button>
|
||||
|
||||
{/* <button type="button" class="btn btn-secondary">6</button> */}
|
||||
</div>
|
||||
<div class="btn-group me-2" role="group" aria-label="Third group">
|
||||
<button type="button" class="btn btn-info">
|
||||
<i class="bi-play" onClick={() => {
|
||||
sendMessage(RunSimulatorSignal(getCustomCookie("userToken")))
|
||||
|
||||
}}/>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="btn-group me-2" role="group" aria-label="Последние 3 симуляции">
|
||||
|
||||
<button type="button" class="btn btn-info">
|
||||
<i class="bi-play"/> Симуляция : 01:00:04
|
||||
</button>
|
||||
|
||||
<button type="button" class="btn btn-info">
|
||||
<i class="bi-play"/> Симуляция : 03:00:04
|
||||
</button>
|
||||
|
||||
<button type="button" class="btn btn-info">
|
||||
<i class="bi-play"/> Симуляция : 03:00:04
|
||||
</button>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
@ -1,209 +0,0 @@
|
||||
import React from 'react';
|
||||
import ModalWindow from '../components/Modal/Modal';
|
||||
|
||||
|
||||
export const ModalAdd = ({isModalAddOpen, objectType, formObjectData, modalAddDroneOrBaseStation, closeAllModals, handleAddInputChange, handleTypeChange}) => {
|
||||
return (
|
||||
<ModalWindow isOpen={isModalAddOpen} onClose={closeAllModals}>
|
||||
<div className="modal d-block" tabIndex="-1">
|
||||
<div className="modal-dialog">
|
||||
<div className="modal-content">
|
||||
<div className="modal-header">
|
||||
<h5 className="modal-title">Добавление объекта</h5>
|
||||
<button type="button" className="btn-close" onClick={closeAllModals}></button>
|
||||
</div>
|
||||
<div className="modal-body">
|
||||
<div className="mb-3">
|
||||
<label>Тип объекта:</label>
|
||||
<div>
|
||||
<div className="form-check form-check-inline">
|
||||
<input
|
||||
className="form-check-input"
|
||||
type="radio"
|
||||
name="objectType"
|
||||
id="drone"
|
||||
checked={objectType === 1}
|
||||
onChange={() => handleTypeChange(1)}
|
||||
/>
|
||||
<label className="form-check-label" htmlFor="drone">Дрон</label>
|
||||
</div>
|
||||
<div className="form-check form-check-inline">
|
||||
<input
|
||||
className="form-check-input"
|
||||
type="radio"
|
||||
name="objectType"
|
||||
id="baseStation"
|
||||
checked={objectType === 2}
|
||||
onChange={() => handleTypeChange(2)}
|
||||
/>
|
||||
<label className="form-check-label" htmlFor="baseStation">Базовая станция</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mb-3">
|
||||
<label>Название объекта:</label>
|
||||
<input
|
||||
type="text"
|
||||
className="form-control"
|
||||
placeholder="Введите название"
|
||||
name='name'
|
||||
value={formObjectData.name}
|
||||
onChange={handleAddInputChange}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="mb-3">
|
||||
<label>Координаты:</label>
|
||||
<div className="input-group mb-2">
|
||||
<span className="input-group-text">X</span>
|
||||
<input
|
||||
type="number"
|
||||
className="form-control"
|
||||
value={formObjectData.coordinates_x}
|
||||
name='coordinates_x'
|
||||
onChange={handleAddInputChange}
|
||||
/>
|
||||
</div>
|
||||
<div className="input-group mb-2">
|
||||
<span className="input-group-text">Y</span>
|
||||
<input
|
||||
type="number"
|
||||
className="form-control"
|
||||
name='coordinates_y'
|
||||
value={formObjectData.coordinates_y}
|
||||
onChange={handleAddInputChange}
|
||||
/>
|
||||
</div>
|
||||
<div className="input-group mb-2">
|
||||
<span className="input-group-text">Z</span>
|
||||
<input
|
||||
type="number"
|
||||
className="form-control"
|
||||
name='coordinates_z'
|
||||
value={formObjectData.coordinates_z}
|
||||
onChange={handleAddInputChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mb-3">
|
||||
<label>Параметры антенны:</label>
|
||||
<div className="input-group mb-2">
|
||||
<span className="input-group-text">Радиус антенны</span>
|
||||
<input
|
||||
type="number"
|
||||
className="form-control"
|
||||
value={formObjectData.antennaRadius}
|
||||
name='antennaRadius'
|
||||
onChange={handleAddInputChange}
|
||||
/>
|
||||
</div>
|
||||
<div className="input-group mb-2">
|
||||
<span className="input-group-text">Направление X</span>
|
||||
<input
|
||||
type="number"
|
||||
className="form-control"
|
||||
value={formObjectData.antennaDirection_x}
|
||||
name='antennaDirection_x'
|
||||
onChange={handleAddInputChange}
|
||||
/>
|
||||
</div>
|
||||
<div className="input-group mb-2">
|
||||
<span className="input-group-text">Направление Y</span>
|
||||
<input
|
||||
type="number"
|
||||
className="form-control"
|
||||
name='antennaDirection_y'
|
||||
value={formObjectData.antennaDirection_y}
|
||||
onChange={handleAddInputChange}
|
||||
/>
|
||||
</div>
|
||||
<div className="input-group mb-2">
|
||||
<span className="input-group-text">Направление Z</span>
|
||||
<input
|
||||
type="number"
|
||||
className="form-control"
|
||||
name='antennaDirection_z'
|
||||
value={formObjectData.antennaDirection_z}
|
||||
onChange={handleAddInputChange}
|
||||
/>
|
||||
</div>
|
||||
<div className="input-group mb-2">
|
||||
<span className="input-group-text">Модуляция</span>
|
||||
<input
|
||||
type="text"
|
||||
className="form-control"
|
||||
name='modulation'
|
||||
value={formObjectData.modulation}
|
||||
onChange={handleAddInputChange}
|
||||
/>
|
||||
</div>
|
||||
<div className="input-group mb-2">
|
||||
<span className="input-group-text">Пропускная способность</span>
|
||||
<input
|
||||
type="number"
|
||||
className="form-control"
|
||||
name='bandwidth'
|
||||
value={formObjectData.bandwidth}
|
||||
onChange={handleAddInputChange}
|
||||
/>
|
||||
</div>
|
||||
<div className="input-group mb-2">
|
||||
<span className="input-group-text">Скорость</span>
|
||||
<input
|
||||
type="number"
|
||||
className="form-control"
|
||||
name='dataRate'
|
||||
value={formObjectData.dataRate}
|
||||
onChange={handleAddInputChange}
|
||||
/>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
{objectType === 1 && (
|
||||
<>
|
||||
<div className="mb-3">
|
||||
<label>Скорость перемещения:</label>
|
||||
<input
|
||||
type="text"
|
||||
className="form-control"
|
||||
name='speed'
|
||||
value={formObjectData.speed}
|
||||
onChange={handleAddInputChange}
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-3">
|
||||
<label>Название Mesh сети:</label>
|
||||
<input
|
||||
type="text"
|
||||
className="form-control"
|
||||
name='meshName'
|
||||
value={formObjectData.meshName}
|
||||
onChange={handleAddInputChange}
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-3">
|
||||
<label>Точки перемещения:</label>
|
||||
<input
|
||||
type="text"
|
||||
className="form-control"
|
||||
name='wayPoints'
|
||||
value={formObjectData.wayPoints}
|
||||
onChange={handleAddInputChange}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
</div>
|
||||
<div className="modal-footer">
|
||||
<button type="button" className="btn btn-secondary" onClick={closeAllModals}>Закрыть</button>
|
||||
<button type="button" className="btn btn-primary" onClick={() => { modalAddDroneOrBaseStation(); }}>Добавить</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ModalWindow>
|
||||
)
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
import React from 'react';
|
||||
import ModalWindow from '../components/Modal/Modal';
|
||||
|
||||
export const ModalDelete = ({ isModalDeleteOpen, closeAllModals, handleDeleteSignal }) => {
|
||||
return (
|
||||
<ModalWindow isOpen={isModalDeleteOpen} onClose={closeAllModals}>
|
||||
<div className="modal-dialog">
|
||||
<div className="modal-content">
|
||||
<div className="modal-header">
|
||||
<h5 className="modal-title">Удаление</h5>
|
||||
</div>
|
||||
<div className="modal-body">
|
||||
<p>Вы точно хотите удалить объект X?</p>
|
||||
</div>
|
||||
<div className="modal-footer">
|
||||
<button type="button" className="btn btn-secondary" onClick={closeAllModals}>Отмена</button>
|
||||
<button type="button" className="btn btn-danger" onClick={() => handleDeleteSignal(1)}>Удалить</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ModalWindow>
|
||||
);
|
||||
};
|
@ -1,26 +0,0 @@
|
||||
import React from 'react';
|
||||
import ModalWindow from '../components/Modal/Modal';
|
||||
|
||||
|
||||
export const ModalJson = ({ isModalOpen, closeAllModals }) => {
|
||||
return (
|
||||
<ModalWindow isOpen={isModalOpen} onClose={closeAllModals}>
|
||||
<div className="modal-dialog">
|
||||
<div className="modal-content">
|
||||
<div className="modal-header">
|
||||
<h5 className="modal-title">Загрузка JSON</h5>
|
||||
<button type="button" className="btn-close" aria-label="Close" onClick={closeAllModals}></button>
|
||||
</div>
|
||||
<div className="modal-body">
|
||||
<p>Выберите или переташите файл для загрузки</p>
|
||||
<input type="file" className="form-control" />
|
||||
</div>
|
||||
<div className="modal-footer">
|
||||
<button type="button" className="btn btn-secondary" onClick={closeAllModals}>Отмена</button>
|
||||
<button type="button" className="btn btn-success" onClick={() => alert('Загружено')}>Загрузить</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ModalWindow>
|
||||
);
|
||||
};
|
@ -1,75 +0,0 @@
|
||||
import React from 'react';
|
||||
import ModalWindow from '../components/Modal/Modal';
|
||||
|
||||
export const ModalMap = ({ isModalMapOpen, closeAllModals, handleAddHeight, formData, handleInputChange, mapSettings, handleSettingsChange }) => {
|
||||
return (
|
||||
<ModalWindow isOpen={isModalMapOpen} onClose={closeAllModals}>
|
||||
<div className="modal-header">
|
||||
<h5 className="modal-title">Настройки карты</h5>
|
||||
<button type="button" className="btn-close" aria-label="Close" onClick={closeAllModals}></button>
|
||||
</div>
|
||||
<div className="modal-body">
|
||||
<div className="row">
|
||||
<div className="col-md-6">
|
||||
<form onSubmit={handleAddHeight} className="mt-3">
|
||||
<div className="mb-3">
|
||||
<label className="form-label">Координата X:</label>
|
||||
<input
|
||||
className="form-control"
|
||||
type="number"
|
||||
name="x"
|
||||
value={formData.x}
|
||||
onChange={handleInputChange}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-3">
|
||||
<label className="form-label">Координата Y:</label>
|
||||
<input
|
||||
className="form-control"
|
||||
type="number"
|
||||
name="y"
|
||||
value={formData.y}
|
||||
onChange={handleInputChange}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-3">
|
||||
<label className="form-label">Высота:</label>
|
||||
<input
|
||||
className="form-control"
|
||||
type="number"
|
||||
name="height"
|
||||
value={formData.height}
|
||||
onChange={handleInputChange}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<button className="btn btn-primary" type="submit">Добавить точку высоты</button>
|
||||
</form>
|
||||
</div>
|
||||
{/* Настройки карты */}
|
||||
<div className="col-md-6">
|
||||
<div className="mt-3">
|
||||
<h5>Настройки карты</h5>
|
||||
<div className="mb-3">
|
||||
<label className="form-label">Максимальная высота:</label>
|
||||
<input
|
||||
className="form-control"
|
||||
type="number"
|
||||
name="maxHeight"
|
||||
value={mapSettings.maxHeight}
|
||||
onChange={handleSettingsChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="modal-footer">
|
||||
<button className="btn btn-secondary" onClick={closeAllModals}>Отмена</button>
|
||||
<button className="btn btn-success" onClick={() => alert('Обновлена карта!')}>Готово</button>
|
||||
</div>
|
||||
</ModalWindow>
|
||||
);
|
||||
};
|
@ -1,29 +0,0 @@
|
||||
import React from 'react';
|
||||
import ModalWindow from '../components/Modal/Modal';
|
||||
|
||||
|
||||
export const ModalSearch = ({ isModalSearchOpen, closeAllModals, handleSearch, searchTerm, setSearchTerm }) => {
|
||||
return (
|
||||
<ModalWindow isOpen={isModalSearchOpen} onClose={closeAllModals}>
|
||||
<div className="modal-dialog">
|
||||
<div className="modal-content">
|
||||
<div className="modal-header">
|
||||
<h5 className="modal-title">Поиск</h5>
|
||||
</div>
|
||||
<div className="modal-body">
|
||||
<input
|
||||
type="text"
|
||||
className="form-control"
|
||||
placeholder="Введите имя"
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="modal-footer">
|
||||
<button type="button" className="btn btn-primary" onClick={handleSearch}>Найти</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ModalWindow>
|
||||
);
|
||||
};
|
@ -1,43 +0,0 @@
|
||||
import React from 'react';
|
||||
import '../UserAccount.scss';
|
||||
|
||||
const PrevCalc = () => {
|
||||
return (
|
||||
<div className="container mt-4">
|
||||
<h2 className="mb-4 text-center">История Калькуляций</h2>
|
||||
<table className="table table-hover table-striped table-bordered">
|
||||
<thead className="thead-dark">
|
||||
<tr>
|
||||
<th scope='col'><abbr title="Уникальный идентификатор в базе данных">UID</abbr></th>
|
||||
<th scope='col'><abbr title="Название">Имя</abbr></th>
|
||||
<th scope='col'>Пользователь</th>
|
||||
<th scope='col'>Время начала</th>
|
||||
<th scope='col'>Время конца</th>
|
||||
<th scope='col'>Действия</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th scope="row">1</th>
|
||||
<td>Калькуляция полета до Варшавы</td>
|
||||
<td>Гитлер</td>
|
||||
<td>1:00</td>
|
||||
<td>4:04</td>
|
||||
<td>
|
||||
<div className="btn-group" role="group">
|
||||
<button className='btn btn-primary'>
|
||||
Загрузить
|
||||
</button>
|
||||
<button className='btn btn-danger ml-2'>
|
||||
Удалить
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default PrevCalc;
|
@ -1,83 +0,0 @@
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
|
||||
export const SimulatorInterface = ({ mouseState, sendMessage, RunSimulatorSignal, getCustomCookie }) => {
|
||||
const [time, setTime] = useState(0);
|
||||
const [simulationState, setSimulationState] = useState({});
|
||||
const [isPlaying, setIsPlaying] = useState(false);
|
||||
const mountRef = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (mountRef.current) {
|
||||
// Инициализация сцены происходит при монтировании компонента
|
||||
initializeScene(mountRef.current);
|
||||
}
|
||||
}, [mountRef.current]);
|
||||
|
||||
const initializeScene = (container) => {
|
||||
// Здесь должна быть логика создания и инициализации сцены внутри контейнера
|
||||
// Например, Three.js сцена
|
||||
console.log('Scene initialized inside container:', container);
|
||||
};
|
||||
|
||||
const handleSliderChange = (event) => {
|
||||
const newValue = event.target.value;
|
||||
setTime(newValue);
|
||||
// Обновляем состояние симуляции на основе времени
|
||||
const newSimulationState = getSimulationState(newValue);
|
||||
setSimulationState(newSimulationState);
|
||||
};
|
||||
|
||||
const handlePlayPause = () => {
|
||||
if (isPlaying) {
|
||||
setIsPlaying(false);
|
||||
} else {
|
||||
setIsPlaying(true);
|
||||
sendMessage(RunSimulatorSignal(getCustomCookie("userToken")));
|
||||
}
|
||||
};
|
||||
|
||||
const getSimulationState = (timeValue) => {
|
||||
// Здесь должна быть логика получения состояния симуляции на основе времени
|
||||
// Например, используйте временную шкалу и объекты, чтобы получить правильное состояние
|
||||
return {};
|
||||
};
|
||||
|
||||
return (
|
||||
<div className='coler-border'>
|
||||
<div>
|
||||
<p> X: {mouseState.x} Y: {mouseState.y} Z: {mouseState.z}</p>
|
||||
</div>
|
||||
<div
|
||||
ref={mountRef}
|
||||
style={{
|
||||
minWidth: '100%',
|
||||
minHeight: '60vh',
|
||||
position: 'relative',
|
||||
}}
|
||||
/>
|
||||
|
||||
<div style={{ display: 'flex', alignItems: 'center', margin: '20px 0' }}>
|
||||
|
||||
<button onClick={handlePlayPause} style={{
|
||||
backgroundColor: '#007bff',
|
||||
color: 'white',
|
||||
border: 'none',
|
||||
padding: '10px 20px',
|
||||
cursor: 'pointer',
|
||||
borderRadius: '4px'
|
||||
}}>
|
||||
{isPlaying ? 'Pause' : 'Play'}
|
||||
</button>
|
||||
<input
|
||||
type="range"
|
||||
value={time}
|
||||
onChange={handleSliderChange}
|
||||
min={0}
|
||||
max={100}
|
||||
step={1}
|
||||
style={{ flexGrow: 1, marginLeft: '20px', marginRight: '20px' }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
@ -1,105 +0,0 @@
|
||||
import React, { useState } from 'react';
|
||||
import '../UserAccount.scss';
|
||||
import {getCustomCookie} from '../Services/Local';
|
||||
const UserAccount = () => {
|
||||
const [userData, setUserData] = useState({
|
||||
username: 'Никита Николаевич',
|
||||
email: 'moxitech@moxitech.com',
|
||||
phone: '+666',
|
||||
});
|
||||
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
const [formData, setFormData] = useState(userData);
|
||||
const [debug, setDebug] = useState(false)
|
||||
|
||||
const enableDebug = () => {
|
||||
if (debug){
|
||||
setDebug(false)
|
||||
} else {
|
||||
setDebug(true)
|
||||
}
|
||||
};
|
||||
|
||||
const handleChange = (e) => {
|
||||
const { name, value } = e.target;
|
||||
setFormData((prevData) => ({
|
||||
...prevData,
|
||||
[name]: value,
|
||||
}));
|
||||
};
|
||||
|
||||
const handleSave = () => {
|
||||
setUserData(formData);
|
||||
setIsEditing(false);
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
setFormData(userData);
|
||||
setIsEditing(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className=" user-account ">
|
||||
<h1>Данные</h1>
|
||||
|
||||
{isEditing ? (
|
||||
<div className="edit-form">
|
||||
<label>
|
||||
Имя пользователя:
|
||||
<input
|
||||
type="text"
|
||||
name="username"
|
||||
value={formData.username}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
Никнейм авторизации:
|
||||
<input
|
||||
type="text"
|
||||
name="email"
|
||||
value={formData.email}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
Пароль:
|
||||
<input
|
||||
type="password"
|
||||
name="phone"
|
||||
value={formData.phone}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</label>
|
||||
<div className="buttons">
|
||||
<button onClick={handleSave}>Сохранить</button>
|
||||
<button onClick={handleCancel}>Отмена</button>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="user-info">
|
||||
<p><strong>Имя:</strong> {userData.username}</p>
|
||||
<p><strong>Email:</strong> {userData.email}</p>
|
||||
<p><strong>Телефон:</strong> {userData.phone}</p>
|
||||
<button className="edit-button" onClick={() => setIsEditing(true)}>
|
||||
Редактировать
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<button className="btn btn-info" onClick={() => enableDebug()}>
|
||||
Turn Debug Mode
|
||||
</button>
|
||||
|
||||
{debug ? <p>
|
||||
debug:
|
||||
UserToken: {getCustomCookie("userToken")}
|
||||
</p>
|
||||
:
|
||||
<p></p>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default UserAccount;
|
@ -1,29 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
const ContextMenu = ({ contextMenu, handleContextMenuClick }) => {
|
||||
if (!contextMenu.visible) return null;
|
||||
|
||||
return (
|
||||
<ul
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: `${contextMenu.y}px`,
|
||||
left: `${contextMenu.x}px`,
|
||||
backgroundColor: 'transparent',
|
||||
listStyle: 'none',
|
||||
padding: '2px',
|
||||
boxShadow: '0px 0px 5px rgba(0,0,0,0.3)',
|
||||
zIndex: 1000,
|
||||
}}
|
||||
>
|
||||
<div className="context">
|
||||
<div className="dot"></div>
|
||||
<li className={"btn"} onClick={() => handleContextMenuClick('add')}>Добавить объект</li>
|
||||
<li className={"btn"} onClick={() => handleContextMenuClick('save')}>Сохранить промежуточный результат</li>
|
||||
<li className={"btn"} onClick={() => handleContextMenuClick('cancel')}>Отмена</li>
|
||||
</div>
|
||||
</ul>
|
||||
);
|
||||
};
|
||||
|
||||
export default ContextMenu;
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user