python invoke sim

This commit is contained in:
moxitech 2024-10-24 14:43:02 +07:00
parent 4d8cde2032
commit f90e40459f
133 changed files with 443 additions and 83091 deletions

5
.env
View File

@ -18,3 +18,8 @@ POSTGRES_HOST=postgres
SOCKET_BASE_ADDRESS=9091
SOCKET_SERVICE_HOST=socket
HOST_API=api.localhost
HOST_WEB=localhost
HOST_WSS=ws.localhost

1
.gitignore vendored
View File

@ -1,3 +1,4 @@
*.env
*/*/node_modules
*/*/.next/
*/*/*/.venv/

View File

View File

@ -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:

View File

@ -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;
}
}
}

View File

@ -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)

File diff suppressed because it is too large Load Diff

View File

@ -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.

Before

Width:  |  Height:  |  Size: 57 KiB

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -1,3 +0,0 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:

View File

@ -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);
}
}

View File

@ -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;

View File

@ -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);
};

View File

@ -1,6 +0,0 @@
const InvokeStartModulation = () => {
}

View File

@ -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
}

View File

@ -1,21 +0,0 @@
import {ServerRequest} from './Server'
export const RequestSaveOnServer = () => {
// Запрос на сохранение результатов на сервере
}
export const RequestRunCalculations = () => {
// Запрос на сохранение результатов на запуск вычислений
}
export const RequestGetClientSettings = () => {
// Запрос на получение клиентских настроек
}
export const RequestUpdateClientSettings = () => {
// Запрос на обновление клиентских настроек
}

View File

@ -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;

View File

@ -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;

View File

@ -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;
}

View File

@ -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;

View File

@ -1,13 +0,0 @@
.main__imager {
height: 100%;
/* background-image: url('../../../public/blueprints.jpg'); */
}
.main__t_b {
color: black;
}
.title {
}

View File

@ -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;
}

View File

@ -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

View File

@ -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

View File

@ -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

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

File diff suppressed because one or more lines are too long

View File

@ -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;
}

View File

@ -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();

View File

@ -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

View File

@ -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;

View File

@ -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>
)}
</>
)
}

View File

@ -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);
}

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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>
)
}

View File

@ -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>
)
}

View File

@ -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>
);
};

View File

@ -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>
);
};

View File

@ -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>
);
};

View File

@ -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>
);
};

View File

@ -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;

View File

@ -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>
);
};

View File

@ -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;

View File

@ -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