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/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",
|
||||
@ -13124,6 +13125,14 @@
|
||||
"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": {
|
||||
"version": "4.0.0",
|
||||
"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/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",
|
||||
|
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 = () => {
|
||||
@ -8,3 +9,18 @@ export const LocalSimulationJson = () => {
|
||||
"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 {
|
||||
width: 200px;
|
||||
height: 100vh;
|
||||
height: 120vh;
|
||||
background-color: #2c3e50;
|
||||
color: white;
|
||||
padding: 20px;
|
||||
|
@ -1,14 +1,14 @@
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import * as THREE from 'three';
|
||||
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 BaseStation from './model/BaseStation';
|
||||
import HeightPoint from './model/HeightPoint';
|
||||
import { RandomHeightPoint } from './helpers/generateRandomHeightPoints'
|
||||
import ModalWindow from './components/Modal/Modal';
|
||||
import './Dashboard.css';
|
||||
// import { NavBar } from './components/Navbar/Navbar';
|
||||
|
||||
|
||||
const Dashboard = () => {
|
||||
const mountRef = useRef(null); // Указатель монтирования
|
||||
const sceneRef = useRef(null); // Указатель на сцену
|
||||
@ -23,10 +23,59 @@ const Dashboard = () => {
|
||||
const [selectedBaseStation, setSelectedBaseStation] = useState(null); // Состояние для выбранной базовой станции
|
||||
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 }); // контекстное меню сцены
|
||||
|
||||
@ -139,6 +188,9 @@ const Dashboard = () => {
|
||||
drones.forEach(droneData => {
|
||||
scene.add(droneData.getObject());
|
||||
});
|
||||
baseStation.forEach(b => {
|
||||
scene.add(b.getObject());
|
||||
});
|
||||
|
||||
// Обработка нажатия ПКМ
|
||||
renderer.domElement.addEventListener('contextmenu', (event) => {
|
||||
@ -168,39 +220,62 @@ const Dashboard = () => {
|
||||
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) => {
|
||||
// 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 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 handleSettingsChange = (event) => {
|
||||
const { name, value } = event.target;
|
||||
setMapSettings({
|
||||
...mapSettings,
|
||||
[name]: value,
|
||||
});
|
||||
};
|
||||
|
||||
// Открытие контекстного меню
|
||||
const handleContextMenuClick = (action) => {
|
||||
if (action === 'add') {
|
||||
if (action === 'add_base'){
|
||||
handleAddBaseStation();
|
||||
}
|
||||
else if (action === 'add') {
|
||||
handleAddDrone();
|
||||
console.log('Добавить объект');
|
||||
} else if (action === 'save') {
|
||||
console.log('Сохранить промежуточный результат');
|
||||
}
|
||||
@ -219,52 +294,237 @@ const Dashboard = () => {
|
||||
<h4><b>Drone</b> Network Simulator - окно разработки</h4>
|
||||
</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}>
|
||||
<h2>Добавление объекта</h2>
|
||||
<input type="text" placeholder="Введите название" />
|
||||
<button onClick={() => alert('Добавлено')}>Добавить</button>
|
||||
<ModalWindow isOpen={modals.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>
|
||||
|
||||
{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 isOpen={isModalOpen} onClose={closeModal}>
|
||||
<ModalWindow isOpen={modals.isModalSearchOpen} onClose={closeAllModals}>
|
||||
<h2>Поиск</h2>
|
||||
<input type="text" placeholder="Введите имя" />
|
||||
<button onClick={() => alert('Добавлено')}>Выбор</button>
|
||||
</ModalWindow>
|
||||
|
||||
<ModalWindow isOpen={isModalOpen} onClose={closeModal}>
|
||||
<ModalWindow isOpen={modals.isModalDeleteOpen} onClose={closeAllModals}>
|
||||
<h2>Удаление</h2>
|
||||
<h2>Вы точно хотите удалить объект X?</h2>
|
||||
<button onClick={closeModal}>Отмена</button>
|
||||
<button onClick={closeAllModals}>Отмена</button>
|
||||
<button onClick={() => alert('Удалено')}>Удалить</button>
|
||||
</ModalWindow>
|
||||
|
||||
<ModalWindow isOpen={isModalOpen} onClose={closeModal}>
|
||||
<ModalWindow isOpen={modals.isModalOpen} onClose={closeAllModals}>
|
||||
<h2>Загрузка JSON</h2>
|
||||
<p>Выберите или переташите файл для загрузки</p>
|
||||
<input type='file'/>
|
||||
<button onClick={closeModal}>Отмена</button>
|
||||
<button onClick={closeAllModals}>Отмена</button>
|
||||
<button onClick={() => alert('Загружено')}>Загрузить</button>
|
||||
</ModalWindow>
|
||||
|
||||
<ModalWindow isOpen={isModalOpen} onClose={closeModal}>
|
||||
{/* Форма для добавления новых высот */}
|
||||
<ModalWindow isOpen={modals.isModalMapOpen} onClose={closeAllModals}>
|
||||
<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>
|
||||
</ModalWindow>
|
||||
|
||||
|
||||
{/* Окно программы */}
|
||||
<div className='coler-border'>
|
||||
<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 ref={mountRef} style={{
|
||||
minWidth: '100%',
|
||||
@ -274,15 +534,15 @@ const Dashboard = () => {
|
||||
<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={openModal} class="btn btn-success">
|
||||
<button type="button" onClick={() => openModal('isModalAddOpen')} class="btn btn-success">
|
||||
<i class="bi-plus"/>Добавить
|
||||
</button>
|
||||
|
||||
<button type="button" onClick={openModal} class="btn btn-success">
|
||||
<button type="button" onClick={() => openModal('isModalDeleteOpen')} class="btn btn-success">
|
||||
<i class="bi-trash"/>Удалить
|
||||
</button>
|
||||
|
||||
<button type="button" onClick={openModal} class="btn btn-success">
|
||||
<button type="button" onClick={() => openModal('isModalSearchOpen')} class="btn btn-success">
|
||||
<i class="bi-search"/>Выбрать
|
||||
</button>
|
||||
|
||||
@ -301,13 +561,13 @@ const Dashboard = () => {
|
||||
<i class="bi-filetype-json"/>Выгрузить
|
||||
</button>
|
||||
|
||||
<button type="button" class="btn btn-secondary">
|
||||
<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" 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">
|
||||
<i class="bi-play"/>
|
||||
</button>
|
||||
@ -348,68 +608,12 @@ const Dashboard = () => {
|
||||
<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>
|
||||
)}
|
||||
<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>
|
||||
);
|
||||
};
|
||||
|
@ -5,6 +5,9 @@ import '../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>
|
||||
|
@ -1,16 +1,26 @@
|
||||
import React, { useState } from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { setCustomCookie, getCustomCookie } from '../Services/Local'
|
||||
|
||||
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 = (e) => {
|
||||
e.preventDefault();
|
||||
// Simple login/password check
|
||||
if (username === 'admin' && password === 'admin') {
|
||||
// TODO :: CHANGE
|
||||
setCustomCookie('userToken', '12345', { expires: 7 }); // Куки с истечением через 7 дней
|
||||
onLogin(); // Call function on successful login
|
||||
|
||||
} else {
|
||||
setError('Неверный логин или пароль');
|
||||
}
|
||||
|
@ -1,49 +1,62 @@
|
||||
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 [rooms, setRooms] = useState(null);
|
||||
const [isCreating, setIsCreating] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
connectSimulation()
|
||||
}, [])
|
||||
getSimulations();
|
||||
setCustomCookie('userToken', '12345', { expires: 7 }); // Куки с истечением через 7 дней
|
||||
|
||||
}, []);
|
||||
|
||||
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 connectSimulation = () => {
|
||||
ServerRequest("/simulations/active", "GET").then( x => {
|
||||
console.log(x.data, x.status)
|
||||
const getSimulations = () => {
|
||||
ServerRequest("/simulations/active", "GET").then(x => {
|
||||
console.log(x.data, x.status);
|
||||
if (x.status === 200) {
|
||||
setRooms(x.data.rooms_ids)
|
||||
setRooms(x.data.rooms_ids);
|
||||
} else {
|
||||
setRooms(null)
|
||||
}
|
||||
})
|
||||
setRooms(null);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className='main__imager' style={{marginTop: "2%"}}>
|
||||
<div className='main__imager' style={{ marginTop: "2%" }}>
|
||||
<div className="row align-items-start">
|
||||
<div className="col">
|
||||
<h1 className="title">Создать новую симуляцию</h1>
|
||||
<hr/>
|
||||
<hr />
|
||||
|
||||
<button className="btn btn-primary" onClick={() => giveMeServerWebsocketRequestAddress()}>
|
||||
<div class="spinner-border text-primary" role="status"/>
|
||||
<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/>
|
||||
<hr />
|
||||
{rooms && rooms.length > 0 ? (
|
||||
rooms.map(room => (
|
||||
<div className='row' key={room}>
|
||||
<div className='row' key={room.uuid}>
|
||||
<div className='col'>
|
||||
<h3>{room.name} | UUID : {room.uuid}</h3>
|
||||
</div>
|
||||
@ -57,11 +70,10 @@ const Main = () => {
|
||||
) : (
|
||||
<p>Пока никто не работает 😒</p>
|
||||
)}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
export default Main;
|
@ -1,4 +1,4 @@
|
||||
import React, { useState } from 'react';
|
||||
import React from 'react';
|
||||
import '../UserAccount.scss';
|
||||
|
||||
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(
|
||||
<div className="modal-overlay" onClick={onClose}>
|
||||
<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>
|
||||
</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