Base station object & cookies & refactoring
This commit is contained in:
parent
7357f02ac6
commit
6b739c3967
9
src/frontend/package-lock.json
generated
9
src/frontend/package-lock.json
generated
@ -12,6 +12,7 @@
|
|||||||
"@testing-library/react": "^13.4.0",
|
"@testing-library/react": "^13.4.0",
|
||||||
"@testing-library/user-event": "^13.5.0",
|
"@testing-library/user-event": "^13.5.0",
|
||||||
"bootstrap-icons": "^1.11.3",
|
"bootstrap-icons": "^1.11.3",
|
||||||
|
"js-cookie": "^3.0.5",
|
||||||
"qrcode.react": "^4.0.1",
|
"qrcode.react": "^4.0.1",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
@ -13124,6 +13125,14 @@
|
|||||||
"jiti": "bin/jiti.js"
|
"jiti": "bin/jiti.js"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/js-cookie": {
|
||||||
|
"version": "3.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz",
|
||||||
|
"integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/js-tokens": {
|
"node_modules/js-tokens": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
"@testing-library/react": "^13.4.0",
|
"@testing-library/react": "^13.4.0",
|
||||||
"@testing-library/user-event": "^13.5.0",
|
"@testing-library/user-event": "^13.5.0",
|
||||||
"bootstrap-icons": "^1.11.3",
|
"bootstrap-icons": "^1.11.3",
|
||||||
|
"js-cookie": "^3.0.5",
|
||||||
"qrcode.react": "^4.0.1",
|
"qrcode.react": "^4.0.1",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
|
BIN
src/frontend/public/phone_tower.glb
Normal file
BIN
src/frontend/public/phone_tower.glb
Normal file
Binary file not shown.
BIN
src/frontend/public/recources/moxitech.webp
Normal file
BIN
src/frontend/public/recources/moxitech.webp
Normal file
Binary file not shown.
After Width: | Height: | Size: 129 KiB |
@ -1,4 +1,5 @@
|
|||||||
|
|
||||||
|
import Cookies from 'js-cookie';
|
||||||
|
|
||||||
|
|
||||||
export const LocalSimulationJson = () => {
|
export const LocalSimulationJson = () => {
|
||||||
@ -8,3 +9,18 @@ export const LocalSimulationJson = () => {
|
|||||||
"TODO": "JSON RETURN"
|
"TODO": "JSON RETURN"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Функция для установки куки
|
||||||
|
export const setCustomCookie = (key, value, options = {}) => {
|
||||||
|
Cookies.set(key, value, { ...options });
|
||||||
|
};
|
||||||
|
|
||||||
|
// Функция для получения куки
|
||||||
|
export const getCustomCookie = (key) => {
|
||||||
|
return Cookies.get(key);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Функция для удаления куки
|
||||||
|
export const removeCustomCookie = (key) => {
|
||||||
|
Cookies.remove(key);
|
||||||
|
};
|
@ -1,6 +1,6 @@
|
|||||||
.sidebar {
|
.sidebar {
|
||||||
width: 200px;
|
width: 200px;
|
||||||
height: 100vh;
|
height: 120vh;
|
||||||
background-color: #2c3e50;
|
background-color: #2c3e50;
|
||||||
color: white;
|
color: white;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
import React, { useEffect, useRef, useState } from 'react';
|
import React, { useEffect, useRef, useState } from 'react';
|
||||||
import * as THREE from 'three';
|
import * as THREE from 'three';
|
||||||
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
|
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
|
||||||
import { FontLoader } from 'three/examples/jsm/loaders/FontLoader';
|
|
||||||
import { TextGeometry } from 'three/examples/jsm/geometries/TextGeometry';
|
|
||||||
import Drone from './model/Drone';
|
import Drone from './model/Drone';
|
||||||
|
import BaseStation from './model/BaseStation';
|
||||||
import HeightPoint from './model/HeightPoint';
|
import HeightPoint from './model/HeightPoint';
|
||||||
import { RandomHeightPoint } from './helpers/generateRandomHeightPoints'
|
import { RandomHeightPoint } from './helpers/generateRandomHeightPoints'
|
||||||
import ModalWindow from './components/Modal/Modal';
|
import ModalWindow from './components/Modal/Modal';
|
||||||
import './Dashboard.css';
|
import './Dashboard.css';
|
||||||
// import { NavBar } from './components/Navbar/Navbar';
|
|
||||||
|
|
||||||
const Dashboard = () => {
|
const Dashboard = () => {
|
||||||
const mountRef = useRef(null); // Указатель монтирования
|
const mountRef = useRef(null); // Указатель монтирования
|
||||||
const sceneRef = useRef(null); // Указатель на сцену
|
const sceneRef = useRef(null); // Указатель на сцену
|
||||||
@ -23,10 +23,59 @@ const Dashboard = () => {
|
|||||||
const [selectedBaseStation, setSelectedBaseStation] = useState(null); // Состояние для выбранной базовой станции
|
const [selectedBaseStation, setSelectedBaseStation] = useState(null); // Состояние для выбранной базовой станции
|
||||||
const [mapSettings, setMapSettings] = useState({maxHeight: 20, coordX: 0, coordY: 0, }); // Настройки карты
|
const [mapSettings, setMapSettings] = useState({maxHeight: 20, coordX: 0, coordY: 0, }); // Настройки карты
|
||||||
|
|
||||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
const [objectType, setObjectType] = useState(1); // 1 = Drone, 2 = Base Station
|
||||||
|
const [formObjectData, setFormObjectData] = useState({
|
||||||
|
name: "",
|
||||||
|
type: 1,
|
||||||
|
coordinates_x: 0,
|
||||||
|
coordinates_y: 0,
|
||||||
|
coordinates_z: 0,
|
||||||
|
droneField1: "",
|
||||||
|
droneField2: "",
|
||||||
|
baseStationField1: "",
|
||||||
|
baseStationField2: ""
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
const handleTypeChange = (type) => {
|
||||||
|
setObjectType(type);
|
||||||
|
setFormData({ ...formData, type });
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Одно состояние для управления всеми модальными окнами
|
||||||
|
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 openModal = () => setIsModalOpen(true);
|
|
||||||
const closeModal = () => setIsModalOpen(false);
|
|
||||||
|
|
||||||
const [contextMenu, setContextMenu] = useState({ visible: false, x: 0, y: 0 }); // контекстное меню сцены
|
const [contextMenu, setContextMenu] = useState({ visible: false, x: 0, y: 0 }); // контекстное меню сцены
|
||||||
|
|
||||||
@ -139,6 +188,9 @@ const Dashboard = () => {
|
|||||||
drones.forEach(droneData => {
|
drones.forEach(droneData => {
|
||||||
scene.add(droneData.getObject());
|
scene.add(droneData.getObject());
|
||||||
});
|
});
|
||||||
|
baseStation.forEach(b => {
|
||||||
|
scene.add(b.getObject());
|
||||||
|
});
|
||||||
|
|
||||||
// Обработка нажатия ПКМ
|
// Обработка нажатия ПКМ
|
||||||
renderer.domElement.addEventListener('contextmenu', (event) => {
|
renderer.domElement.addEventListener('contextmenu', (event) => {
|
||||||
@ -168,39 +220,62 @@ const Dashboard = () => {
|
|||||||
sceneRef.current.add(drone.getObject());
|
sceneRef.current.add(drone.getObject());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
// Добавление дрона в сцену
|
||||||
|
const handleAddBaseStation = () => {
|
||||||
|
if (sceneRef.current) {
|
||||||
|
const current = Date.now();
|
||||||
|
const base = new BaseStation(current / 1000);
|
||||||
|
base.setPosition(Math.random() * 20 - 10, 20, Math.random() * 20 - 10);
|
||||||
|
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 handleInputChange = (event) => {
|
|
||||||
// const { name, value } = event.target;
|
|
||||||
// setFormData({
|
|
||||||
// ...formData,
|
|
||||||
// [name]: value,
|
|
||||||
// });
|
|
||||||
// };
|
|
||||||
|
|
||||||
// Добавление элемента карты высот (высоты)
|
// Добавление элемента карты высот (высоты)
|
||||||
// const handleAddHeight = (event) => {
|
const handleAddHeight = (event) => {
|
||||||
// event.preventDefault();
|
event.preventDefault();
|
||||||
// const { x, y, height } = formData;
|
const { x, y, height } = formData;
|
||||||
// const newPoint = new HeightPoint(parseInt(x), parseInt(y), parseFloat(height));
|
const newPoint = new HeightPoint(parseInt(x), parseInt(y), parseFloat(height));
|
||||||
// setHeightData((prevData) => [...prevData, newPoint]);
|
setHeightData((prevData) => [...prevData, newPoint]);
|
||||||
// setFormData({ x: '', y: '', height: '' });
|
setFormData({ x: '', y: '', height: '' });
|
||||||
// };
|
};
|
||||||
|
|
||||||
// Изменение настроек
|
// Изменение настроек
|
||||||
// const handleSettingsChange = (event) => {
|
const handleSettingsChange = (event) => {
|
||||||
// const { name, value } = event.target;
|
const { name, value } = event.target;
|
||||||
// setMapSettings({
|
setMapSettings({
|
||||||
// ...mapSettings,
|
...mapSettings,
|
||||||
// [name]: value,
|
[name]: value,
|
||||||
// });
|
});
|
||||||
// };
|
};
|
||||||
|
|
||||||
// Открытие контекстного меню
|
// Открытие контекстного меню
|
||||||
const handleContextMenuClick = (action) => {
|
const handleContextMenuClick = (action) => {
|
||||||
if (action === 'add') {
|
if (action === 'add_base'){
|
||||||
|
handleAddBaseStation();
|
||||||
|
}
|
||||||
|
else if (action === 'add') {
|
||||||
handleAddDrone();
|
handleAddDrone();
|
||||||
console.log('Добавить объект');
|
|
||||||
} else if (action === 'save') {
|
} else if (action === 'save') {
|
||||||
console.log('Сохранить промежуточный результат');
|
console.log('Сохранить промежуточный результат');
|
||||||
}
|
}
|
||||||
@ -219,52 +294,237 @@ const Dashboard = () => {
|
|||||||
<h4><b>Drone</b> Network Simulator - окно разработки</h4>
|
<h4><b>Drone</b> Network Simulator - окно разработки</h4>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* <NavBar/> TEST */}
|
|
||||||
{/* <button onClick={openModal}>Открыть модальное окно</button>
|
|
||||||
<ModalWindow isOpen={isModalOpen} onClose={closeModal}>
|
|
||||||
<h2>Кастомное содержимое</h2>
|
|
||||||
<input type="text" placeholder="Введите текст" />
|
|
||||||
<button onClick={() => alert('Действие!')}>Добавить</button>
|
|
||||||
</ModalWindow> */}
|
|
||||||
{/* END NAVBAR TEST */}
|
|
||||||
|
|
||||||
<ModalWindow isOpen={isModalOpen} onClose={closeModal}>
|
<ModalWindow isOpen={modals.isModalAddOpen} onClose={closeAllModals}>
|
||||||
<h2>Добавление объекта</h2>
|
<div className="modal d-block" tabIndex="-1">
|
||||||
<input type="text" placeholder="Введите название" />
|
<div className="modal-dialog">
|
||||||
<button onClick={() => alert('Добавлено')}>Добавить</button>
|
<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>
|
||||||
|
|
||||||
|
{objectType === 1 && (
|
||||||
|
<>
|
||||||
|
<div className="mb-3">
|
||||||
|
<label>Поле 1 для дрона:</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
className="form-control"
|
||||||
|
name='droneField1'
|
||||||
|
value={formObjectData.droneField1}
|
||||||
|
onChange={handleAddInputChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="mb-3">
|
||||||
|
<label>Поле 2 для дрона:</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
className="form-control"
|
||||||
|
name='droneField2'
|
||||||
|
value={formObjectData.droneField2}
|
||||||
|
onChange={handleAddInputChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{objectType === 2 && (
|
||||||
|
<>
|
||||||
|
<div className="mb-3">
|
||||||
|
<label>Поле 1 для базовой станции:</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
className="form-control"
|
||||||
|
name='baseStationField1'
|
||||||
|
value={formObjectData.baseStationField1}
|
||||||
|
onChange={handleAddInputChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="mb-3">
|
||||||
|
<label>Поле 2 для базовой станции:</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
className="form-control"
|
||||||
|
name='baseStationField2'
|
||||||
|
value={formObjectData.baseStationField2}
|
||||||
|
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={() => { alert(`Добавлено: ${JSON.stringify(formData)}`); }}>Добавить</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</ModalWindow>
|
</ModalWindow>
|
||||||
|
|
||||||
<ModalWindow isOpen={isModalOpen} onClose={closeModal}>
|
<ModalWindow isOpen={modals.isModalSearchOpen} onClose={closeAllModals}>
|
||||||
<h2>Поиск</h2>
|
<h2>Поиск</h2>
|
||||||
<input type="text" placeholder="Введите имя" />
|
<input type="text" placeholder="Введите имя" />
|
||||||
<button onClick={() => alert('Добавлено')}>Выбор</button>
|
<button onClick={() => alert('Добавлено')}>Выбор</button>
|
||||||
</ModalWindow>
|
</ModalWindow>
|
||||||
|
|
||||||
<ModalWindow isOpen={isModalOpen} onClose={closeModal}>
|
<ModalWindow isOpen={modals.isModalDeleteOpen} onClose={closeAllModals}>
|
||||||
<h2>Удаление</h2>
|
<h2>Удаление</h2>
|
||||||
<h2>Вы точно хотите удалить объект X?</h2>
|
<h2>Вы точно хотите удалить объект X?</h2>
|
||||||
<button onClick={closeModal}>Отмена</button>
|
<button onClick={closeAllModals}>Отмена</button>
|
||||||
<button onClick={() => alert('Удалено')}>Удалить</button>
|
<button onClick={() => alert('Удалено')}>Удалить</button>
|
||||||
</ModalWindow>
|
</ModalWindow>
|
||||||
|
|
||||||
<ModalWindow isOpen={isModalOpen} onClose={closeModal}>
|
<ModalWindow isOpen={modals.isModalOpen} onClose={closeAllModals}>
|
||||||
<h2>Загрузка JSON</h2>
|
<h2>Загрузка JSON</h2>
|
||||||
<p>Выберите или переташите файл для загрузки</p>
|
<p>Выберите или переташите файл для загрузки</p>
|
||||||
<input type='file'/>
|
<input type='file'/>
|
||||||
<button onClick={closeModal}>Отмена</button>
|
<button onClick={closeAllModals}>Отмена</button>
|
||||||
<button onClick={() => alert('Загружено')}>Загрузить</button>
|
<button onClick={() => alert('Загружено')}>Загрузить</button>
|
||||||
</ModalWindow>
|
</ModalWindow>
|
||||||
|
|
||||||
<ModalWindow isOpen={isModalOpen} onClose={closeModal}>
|
{/* Форма для добавления новых высот */}
|
||||||
|
<ModalWindow isOpen={modals.isModalMapOpen} onClose={closeAllModals}>
|
||||||
<h2>Настройки карты</h2>
|
<h2>Настройки карты</h2>
|
||||||
<button onClick={closeModal}>Отмена</button>
|
<div className="columns">
|
||||||
|
<div className='column'>
|
||||||
|
<form onSubmit={handleAddHeight} style={{ marginTop: '20px' }}>
|
||||||
|
<div>
|
||||||
|
<label>Координата X:</label>
|
||||||
|
<input
|
||||||
|
className="input is-primary"
|
||||||
|
type="number"
|
||||||
|
name="x"
|
||||||
|
value={formData.x}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label>Координата Y:</label>
|
||||||
|
<input
|
||||||
|
className="input is-primary"
|
||||||
|
type="number"
|
||||||
|
name="y"
|
||||||
|
value={formData.y}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label>Высота:</label>
|
||||||
|
<input
|
||||||
|
className="input is-primary"
|
||||||
|
type="number"
|
||||||
|
name="height"
|
||||||
|
value={formData.height}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<button className='button' type="submit">Добавить точку высоты</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{/* Настройки карты */}
|
||||||
|
<div className='column'>
|
||||||
|
<div style={{ marginTop: '20px' }}>
|
||||||
|
<h2>Настройки карты</h2>
|
||||||
|
<div>
|
||||||
|
<label>Максимальная высота:</label>
|
||||||
|
<input
|
||||||
|
className="input"
|
||||||
|
type="number"
|
||||||
|
name="maxHeight"
|
||||||
|
value={mapSettings.maxHeight}
|
||||||
|
onChange={handleSettingsChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button onClick={closeAllModals}>Отмена</button>
|
||||||
<button onClick={() => alert('Обновлена карта!')}>Готово</button>
|
<button onClick={() => alert('Обновлена карта!')}>Готово</button>
|
||||||
</ModalWindow>
|
</ModalWindow>
|
||||||
|
|
||||||
|
{/* Окно программы */}
|
||||||
<div className='coler-border'>
|
<div className='coler-border'>
|
||||||
<div>
|
<div>
|
||||||
<p>X: {mouseState.x} Y: {mouseState.y} Z: {mouseState.z}</p>
|
<p> X: {mouseState.x} Y: {mouseState.y} Z: {mouseState.z}</p>
|
||||||
</div>
|
</div>
|
||||||
<div ref={mountRef} style={{
|
<div ref={mountRef} style={{
|
||||||
minWidth: '100%',
|
minWidth: '100%',
|
||||||
@ -274,15 +534,15 @@ const Dashboard = () => {
|
|||||||
<div class="btn-toolbar" role="toolbar" aria-label="Toolbar with button groups">
|
<div class="btn-toolbar" role="toolbar" aria-label="Toolbar with button groups">
|
||||||
<div class="btn-group me-2" role="group" aria-label="First group">
|
<div class="btn-group me-2" role="group" aria-label="First group">
|
||||||
|
|
||||||
<button type="button" onClick={openModal} class="btn btn-success">
|
<button type="button" onClick={() => openModal('isModalAddOpen')} class="btn btn-success">
|
||||||
<i class="bi-plus"/>Добавить
|
<i class="bi-plus"/>Добавить
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button type="button" onClick={openModal} class="btn btn-success">
|
<button type="button" onClick={() => openModal('isModalDeleteOpen')} class="btn btn-success">
|
||||||
<i class="bi-trash"/>Удалить
|
<i class="bi-trash"/>Удалить
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button type="button" onClick={openModal} class="btn btn-success">
|
<button type="button" onClick={() => openModal('isModalSearchOpen')} class="btn btn-success">
|
||||||
<i class="bi-search"/>Выбрать
|
<i class="bi-search"/>Выбрать
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
@ -301,13 +561,13 @@ const Dashboard = () => {
|
|||||||
<i class="bi-filetype-json"/>Выгрузить
|
<i class="bi-filetype-json"/>Выгрузить
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button type="button" class="btn btn-secondary">
|
<button type="button" onClick={() => openModal('isModalMapOpen')} class="btn btn-secondary">
|
||||||
<i class="bi-map"/>Настройки карты
|
<i class="bi-map"/>Настройки карты
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{/* <button type="button" class="btn btn-secondary">6</button> */}
|
{/* <button type="button" class="btn btn-secondary">6</button> */}
|
||||||
</div>
|
</div>
|
||||||
<div class="btn-group" role="group" aria-label="Third group">
|
<div class="btn-group me-2" role="group" aria-label="Third group">
|
||||||
<button type="button" class="btn btn-info">
|
<button type="button" class="btn btn-info">
|
||||||
<i class="bi-play"/>
|
<i class="bi-play"/>
|
||||||
</button>
|
</button>
|
||||||
@ -348,68 +608,12 @@ const Dashboard = () => {
|
|||||||
<div className='context'>
|
<div className='context'>
|
||||||
<div class="dot"></div>
|
<div class="dot"></div>
|
||||||
<li className={"btn"} onClick={() => handleContextMenuClick('add')}>Добавить объект</li>
|
<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('save')}>Сохранить промежуточный результат</li>
|
||||||
<li className={"btn"} onClick={() => handleContextMenuClick('cancel')}>Отмена</li>
|
<li className={"btn"} onClick={() => handleContextMenuClick('cancel')}>Отмена</li>
|
||||||
</div>
|
</div>
|
||||||
</ul>
|
</ul>
|
||||||
)}
|
)}
|
||||||
<div className="columns">
|
|
||||||
{/* Форма для добавления новых высот */}
|
|
||||||
{/* <div className='column'>
|
|
||||||
<form onSubmit={handleAddHeight} style={{ marginTop: '20px' }}>
|
|
||||||
<div>
|
|
||||||
<label>Координата X:</label>
|
|
||||||
<input
|
|
||||||
className="input is-primary"
|
|
||||||
type="number"
|
|
||||||
name="x"
|
|
||||||
value={formData.x}
|
|
||||||
onChange={handleInputChange}
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label>Координата Y:</label>
|
|
||||||
<input
|
|
||||||
className="input is-primary"
|
|
||||||
type="number"
|
|
||||||
name="y"
|
|
||||||
value={formData.y}
|
|
||||||
onChange={handleInputChange}
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label>Высота:</label>
|
|
||||||
<input
|
|
||||||
className="input is-primary"
|
|
||||||
type="number"
|
|
||||||
name="height"
|
|
||||||
value={formData.height}
|
|
||||||
onChange={handleInputChange}
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<button className='button' type="submit">Добавить точку высоты</button>
|
|
||||||
</form>
|
|
||||||
</div> */}
|
|
||||||
{/* Настройки карты */}
|
|
||||||
{/* <div className='column'>
|
|
||||||
<div style={{ marginTop: '20px' }}>
|
|
||||||
<h2>Настройки карты</h2>
|
|
||||||
<div>
|
|
||||||
<label>Максимальная высота:</label>
|
|
||||||
<input
|
|
||||||
className="input"
|
|
||||||
type="number"
|
|
||||||
name="maxHeight"
|
|
||||||
value={mapSettings.maxHeight}
|
|
||||||
onChange={handleSettingsChange}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div> */}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -5,6 +5,9 @@ import '../Devices.css';
|
|||||||
const Devices = () => {
|
const Devices = () => {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
<p>Разработчик: @moxitech</p>
|
||||||
|
<img style={{ width: 200, height: 200 }} src='/recources/moxitech.webp'></img>
|
||||||
|
<hr/>
|
||||||
<button className='button is-danger'>Удалить неиспользуемые шаблоны</button>
|
<button className='button is-danger'>Удалить неиспользуемые шаблоны</button>
|
||||||
<hr/>
|
<hr/>
|
||||||
<button className='button is-danger'>Удалить неактивных пользователей</button>
|
<button className='button is-danger'>Удалить неактивных пользователей</button>
|
||||||
|
@ -1,16 +1,26 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { setCustomCookie, getCustomCookie } from '../Services/Local'
|
||||||
|
|
||||||
import '../Login.scss';
|
import '../Login.scss';
|
||||||
|
|
||||||
const Login = ({ onLogin }) => {
|
const Login = ({ onLogin }) => {
|
||||||
const [username, setUsername] = useState('');
|
const [username, setUsername] = useState('');
|
||||||
const [password, setPassword] = useState('');
|
const [password, setPassword] = useState('');
|
||||||
const [error, setError] = useState('');
|
const [error, setError] = useState('');
|
||||||
|
useEffect(() => {
|
||||||
|
let cook = getCustomCookie('userToken')
|
||||||
|
if (cook) {
|
||||||
|
onLogin();
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
const handleSubmit = (e) => {
|
const handleSubmit = (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
// Simple login/password check
|
// Simple login/password check
|
||||||
if (username === 'admin' && password === 'admin') {
|
if (username === 'admin' && password === 'admin') {
|
||||||
|
// TODO :: CHANGE
|
||||||
|
setCustomCookie('userToken', '12345', { expires: 7 }); // Куки с истечением через 7 дней
|
||||||
onLogin(); // Call function on successful login
|
onLogin(); // Call function on successful login
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
setError('Неверный логин или пароль');
|
setError('Неверный логин или пароль');
|
||||||
}
|
}
|
||||||
|
@ -1,49 +1,62 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import '../css/Custom/Main.css';
|
import '../css/Custom/Main.css';
|
||||||
import { ServerRequest, giveMeServerWebsocketRequestAddress } from '../Services/Server';
|
import { ServerRequest, giveMeServerWebsocketRequestAddress } from '../Services/Server';
|
||||||
|
import { setCustomCookie, getCustomCookie } from '../Services/Local'
|
||||||
const Main = () => {
|
const Main = () => {
|
||||||
const [rooms, setRooms] = useState(null)
|
const [rooms, setRooms] = useState(null);
|
||||||
|
const [isCreating, setIsCreating] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
connectSimulation()
|
getSimulations();
|
||||||
}, [])
|
setCustomCookie('userToken', '12345', { expires: 7 }); // Куки с истечением через 7 дней
|
||||||
|
|
||||||
|
}, []);
|
||||||
|
|
||||||
const createSimulation = () => {
|
const createSimulation = () => {
|
||||||
|
setIsCreating(true);
|
||||||
|
giveMeServerWebsocketRequestAddress();
|
||||||
|
// TODO: Make POST request to create simulation here.
|
||||||
// POST $BASE_ADDR/createSimulation?userToken=?
|
// POST $BASE_ADDR/createSimulation?userToken=?
|
||||||
|
|
||||||
}
|
// Simulating an async request for demo purposes
|
||||||
|
setTimeout(() => {
|
||||||
|
setIsCreating(false);
|
||||||
|
}, 2000);
|
||||||
|
};
|
||||||
|
|
||||||
const connectSimulation = () => {
|
const getSimulations = () => {
|
||||||
ServerRequest("/simulations/active", "GET").then( x => {
|
ServerRequest("/simulations/active", "GET").then(x => {
|
||||||
console.log(x.data, x.status)
|
console.log(x.data, x.status);
|
||||||
if (x.status === 200) {
|
if (x.status === 200) {
|
||||||
setRooms(x.data.rooms_ids)
|
setRooms(x.data.rooms_ids);
|
||||||
} else {
|
} else {
|
||||||
setRooms(null)
|
setRooms(null);
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='main__imager' style={{marginTop: "2%"}}>
|
<div className='main__imager' style={{ marginTop: "2%" }}>
|
||||||
<div className="row align-items-start">
|
<div className="row align-items-start">
|
||||||
<div className="col">
|
<div className="col">
|
||||||
<h1 className="title">Создать новую симуляцию</h1>
|
<h1 className="title">Создать новую симуляцию</h1>
|
||||||
<hr/>
|
<hr />
|
||||||
|
|
||||||
<button className="btn btn-primary" onClick={() => giveMeServerWebsocketRequestAddress()}>
|
<button
|
||||||
<div class="spinner-border text-primary" role="status"/>
|
className="btn btn-primary"
|
||||||
|
onClick={createSimulation}
|
||||||
|
disabled={isCreating}
|
||||||
|
>
|
||||||
|
{isCreating && <div className="spinner-border text-light me-2" role="status" />}
|
||||||
Create
|
Create
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div className="col">
|
<div className="col">
|
||||||
<h1 className="title">Присоединиться к моделированию</h1>
|
<h1 className="title">Присоединиться к моделированию</h1>
|
||||||
<hr/>
|
<hr />
|
||||||
{rooms && rooms.length > 0 ? (
|
{rooms && rooms.length > 0 ? (
|
||||||
rooms.map(room => (
|
rooms.map(room => (
|
||||||
<div className='row' key={room}>
|
<div className='row' key={room.uuid}>
|
||||||
<div className='col'>
|
<div className='col'>
|
||||||
<h3>{room.name} | UUID : {room.uuid}</h3>
|
<h3>{room.name} | UUID : {room.uuid}</h3>
|
||||||
</div>
|
</div>
|
||||||
@ -57,11 +70,10 @@ const Main = () => {
|
|||||||
) : (
|
) : (
|
||||||
<p>Пока никто не работает 😒</p>
|
<p>Пока никто не работает 😒</p>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Main;
|
export default Main;
|
@ -1,4 +1,4 @@
|
|||||||
import React, { useState } from 'react';
|
import React from 'react';
|
||||||
import '../UserAccount.scss';
|
import '../UserAccount.scss';
|
||||||
|
|
||||||
const PrevCalc = () => {
|
const PrevCalc = () => {
|
||||||
|
29
src/frontend/src/pages/components/ContextMenu/ContextMenu.js
Normal file
29
src/frontend/src/pages/components/ContextMenu/ContextMenu.js
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
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;
|
@ -20,7 +20,7 @@ const ModalWindow = ({ isOpen, onClose, children }) => {
|
|||||||
return ReactDOM.createPortal(
|
return ReactDOM.createPortal(
|
||||||
<div className="modal-overlay" onClick={onClose}>
|
<div className="modal-overlay" onClick={onClose}>
|
||||||
<div className="modal-window" onClick={(e) => e.stopPropagation()}>
|
<div className="modal-window" onClick={(e) => e.stopPropagation()}>
|
||||||
<button className="modal-close-button" onClick={onClose}>×</button>
|
<button className="btn-close" onClick={onClose}></button>
|
||||||
<div className="modal-content">{children}</div>
|
<div className="modal-content">{children}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>,
|
</div>,
|
||||||
|
40
src/frontend/src/pages/components/Toolbar/Toolbar.js
Normal file
40
src/frontend/src/pages/components/Toolbar/Toolbar.js
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
const Toolbar = ({ openModal }) => {
|
||||||
|
return (
|
||||||
|
<div className="btn-toolbar" role="toolbar" aria-label="Toolbar with button groups">
|
||||||
|
<div className="btn-group me-2" role="group">
|
||||||
|
<button type="button" onClick={() => openModal('isModalAddOpen')} className="btn btn-success">
|
||||||
|
<i className="bi-plus" />Добавить
|
||||||
|
</button>
|
||||||
|
<button type="button" onClick={() => openModal('isModalDeleteOpen')} className="btn btn-success">
|
||||||
|
<i className="bi-trash" />Удалить
|
||||||
|
</button>
|
||||||
|
<button type="button" onClick={() => openModal('isModalSearchOpen')} className="btn btn-success">
|
||||||
|
<i className="bi-search" />Выбрать
|
||||||
|
</button>
|
||||||
|
<button type="button" className="btn btn-success">
|
||||||
|
<i className="bi-save" />Сохранить
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className="btn-group me-2" role="group">
|
||||||
|
<button type="button" className="btn btn-secondary">
|
||||||
|
<i className="bi-box-arrow-in-down" />Загрузить данные
|
||||||
|
</button>
|
||||||
|
<button type="button" className="btn btn-secondary">
|
||||||
|
<i className="bi-filetype-json" />Выгрузить
|
||||||
|
</button>
|
||||||
|
<button type="button" onClick={() => openModal('isModalMapOpen')} className="btn btn-secondary">
|
||||||
|
<i className="bi-map" />Настройки карты
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className="btn-group me-2" role="group">
|
||||||
|
<button type="button" className="btn btn-info">
|
||||||
|
<i className="bi-play" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Toolbar;
|
113
src/frontend/src/pages/hooks/ThreeJSScene.js
Normal file
113
src/frontend/src/pages/hooks/ThreeJSScene.js
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
import React, { useEffect, useRef } from 'react';
|
||||||
|
import * as THREE from 'three';
|
||||||
|
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
|
||||||
|
import Drone from '../../model/Drone';
|
||||||
|
|
||||||
|
const ThreeJSScene = ({ heightData, mapSettings, drones, setDrones, setSelectedDrone }) => {
|
||||||
|
const mountRef = useRef(null);
|
||||||
|
const sceneRef = useRef(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!mountRef.current) return;
|
||||||
|
let width = mountRef.current.clientWidth;
|
||||||
|
let height = mountRef.current.clientHeight;
|
||||||
|
const scene = new THREE.Scene();
|
||||||
|
sceneRef.current = scene;
|
||||||
|
const camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 2000);
|
||||||
|
camera.position.set(0, 80, 120);
|
||||||
|
|
||||||
|
const renderer = new THREE.WebGLRenderer();
|
||||||
|
renderer.setSize(width, height);
|
||||||
|
mountRef.current.appendChild(renderer.domElement);
|
||||||
|
|
||||||
|
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(10);
|
||||||
|
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 handleClick = (event) => {
|
||||||
|
const mouse = new THREE.Vector2(
|
||||||
|
(event.clientX / window.innerWidth) * 2 - 1,
|
||||||
|
-(event.clientY / window.innerHeight) * 2 + 1
|
||||||
|
);
|
||||||
|
const raycaster = new THREE.Raycaster();
|
||||||
|
raycaster.setFromCamera(mouse, camera);
|
||||||
|
|
||||||
|
const intersects = raycaster.intersectObjects(drones.map(drone => drone.getObject()), true);
|
||||||
|
if (intersects.length > 0) {
|
||||||
|
const selected = intersects[0].object;
|
||||||
|
const drone = drones.find(drone => drone.getObject().children[0] === selected);
|
||||||
|
if (drone) {
|
||||||
|
if (setSelectedDrone) {
|
||||||
|
setSelectedDrone(drone);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (setSelectedDrone) {
|
||||||
|
setSelectedDrone(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const animate = () => {
|
||||||
|
requestAnimationFrame(animate);
|
||||||
|
controls.update();
|
||||||
|
renderer.render(scene, camera);
|
||||||
|
};
|
||||||
|
|
||||||
|
renderer.domElement.addEventListener('click', handleClick);
|
||||||
|
animate();
|
||||||
|
|
||||||
|
drones.forEach(droneData => {
|
||||||
|
scene.add(droneData.getObject());
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (mountRef.current) {
|
||||||
|
mountRef.current.removeChild(renderer.domElement);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [drones, heightData, mapSettings]);
|
||||||
|
|
||||||
|
return <div ref={mountRef} style={{ minWidth: '100%', minHeight: '60vh', position: 'relative' }} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ThreeJSScene;
|
30
src/frontend/src/pages/model/BaseStation.js
Normal file
30
src/frontend/src/pages/model/BaseStation.js
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import * as THREE from 'three';
|
||||||
|
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
|
||||||
|
class BaseStation {
|
||||||
|
constructor(name, scale = 10) {
|
||||||
|
this.object = new THREE.Object3D();
|
||||||
|
this.name = name;
|
||||||
|
|
||||||
|
// Инициализируем GLTFLoader
|
||||||
|
const loader = new GLTFLoader();
|
||||||
|
|
||||||
|
// Загружаем модель из файла GLTF
|
||||||
|
loader.load("/phone_tower.glb", (gltf) => {
|
||||||
|
this.object.add(gltf.scene); // Добавляем сцену из GLTF к объекту дрона
|
||||||
|
gltf.scene.scale.set(scale, scale, scale); // Применяем масштаб
|
||||||
|
}, undefined, (error) => {
|
||||||
|
console.error("Ошибка загрузки GLTF:", error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
setPosition(x, y, z) {
|
||||||
|
this.object.position.set(x, y, z);
|
||||||
|
}
|
||||||
|
|
||||||
|
getObject() {
|
||||||
|
return this.object;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export default BaseStation;
|
Loading…
Reference in New Issue
Block a user