Initial commit
This commit is contained in:
commit
b7d90b9afe
16245 files changed
+3396666
No files matched your search
Executable
+44
@@ -0,0 +1,44 @@
|
||||
# TME Simulator Frontend
|
||||
|
||||
TME Simulator is a web application built with React.
|
||||
|
||||
## Getting Started
|
||||
|
||||
These instructions will get you a copy of the project up and running on your local machine for development and testing purposes.
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Node.js
|
||||
- npm
|
||||
|
||||
### Installation
|
||||
|
||||
1. Clone the repository
|
||||
2. Navigate to the project directory
|
||||
3. Install dependencies with npm i
|
||||
|
||||
## 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 the browser. The page will reload if you make edits. You will also see any lint errors in the console.
|
||||
|
||||
### `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!
|
||||
|
||||
## Deployment
|
||||
|
||||
See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
|
||||
|
||||
## Contributing
|
||||
|
||||
Please read [CONTRIBUTING.md](CONTRIBUTING.md) for details on our code of conduct, and the process for submitting pull requests to us.
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the MIT License - see the [LICENSE.md](LICENSE.md) file for details
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"name": "frontend",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@reduxjs/toolkit": "^2.2.5",
|
||||
"@testing-library/jest-dom": "^5.17.0",
|
||||
"@testing-library/react": "^13.4.0",
|
||||
"@testing-library/user-event": "^13.5.0",
|
||||
"@turf/turf": "^7.0.0",
|
||||
"cra-template": "1.2.0",
|
||||
"immer": "^10.1.1",
|
||||
"leaflet-draw": "^1.0.4",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-leaflet": "^4.2.1",
|
||||
"react-leaflet-draw": "^0.20.4",
|
||||
"react-redux": "^9.1.2",
|
||||
"react-router-dom": "^6.23.1",
|
||||
"react-scripts": "^5.0.1",
|
||||
"web-vitals": "^2.1.4"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "HOST=localhost react-scripts start",
|
||||
"build": "react-scripts build"
|
||||
},
|
||||
"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"
|
||||
]
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.8 KiB |
+19
@@ -0,0 +1,19 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%PUBLIC_URL%/RheinmetallLogo.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Website for simulating soldiers in the Legatus Excon"
|
||||
/>
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||
<title>Legatus Simulator Software | Frontend</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
</html>
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"short_name": "Legatus Simulator Software | Frontend",
|
||||
"name": "Website for simulating soldiers in the Legatus Excon",
|
||||
"icons": [
|
||||
{
|
||||
"src": "RheinmetallLogo.png",
|
||||
"sizes": "64x64 32x32 24x24 16x16",
|
||||
"type": "image/x-icon"
|
||||
}
|
||||
],
|
||||
"start_url": ".",
|
||||
"display": "standalone",
|
||||
"theme_color": "#000000",
|
||||
"background_color": "#ffffff"
|
||||
}
|
||||
Executable
+13
@@ -0,0 +1,13 @@
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
html body {
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.leaflet-draw.leaflet-control {
|
||||
display: none;
|
||||
}
|
||||
Executable
+27
@@ -0,0 +1,27 @@
|
||||
import React, { useState } from "react"
|
||||
import './App.css';
|
||||
import { useDispatch } from "react-redux";
|
||||
import { useGetExerciseIds } from "./websocket/getExerciseIds";
|
||||
import { ErrorPopup } from "./components/errorPopup";
|
||||
import MapBox from './components/map'
|
||||
import SideBar from "./components/sideBar";
|
||||
import Popup from "./components/popup";
|
||||
import { useGetPartyData } from "./websocket/getPartyData";
|
||||
|
||||
function App() {
|
||||
const [createPopupOpen, setCreatePopupOpen] = useState(false)
|
||||
|
||||
const dispatch = useDispatch()
|
||||
useGetExerciseIds(dispatch)
|
||||
useGetPartyData(42, dispatch)
|
||||
return (
|
||||
<div>
|
||||
<ErrorPopup />
|
||||
<Popup createPopupState={createPopupOpen} setPopupState={setCreatePopupOpen} />
|
||||
<SideBar createPopupStateChanger={setCreatePopupOpen} />
|
||||
<MapBox />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
@@ -0,0 +1,63 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Rheinmetall_Group_4c_positiv"
|
||||
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="4656.32px"
|
||||
height="1268.483px" viewBox="0 0 4656.32 1268.483" enable-background="new 0 0 4656.32 1268.483" xml:space="preserve">
|
||||
<path id="RHEINMETALL_1_" fill="#3E3D40" d="M955.22,1044.451h-48.742c-9.427,0-15.275-5.268-15.275-17.631V696.106
|
||||
c0-44.62,16.423-66.955,65.189-66.955H1001c57.589,0,96.966,5.306,119.287,14.117c44.621,18.201,59.921,59.328,59.921,109.823
|
||||
c0,50.53-17.064,92.805-66.399,113.364l79.911,144.521c3.505,6.453,8.193,14.091,8.193,21.15c0,5.887-4.083,9.981-8.773,10.575
|
||||
c-5.294,1.184-46.446,2.925-58.773,2.925c-10.563,0-16.424-5.282-20.558-13.5l-76.949-150.963
|
||||
c-9.972,0.593-20.583,0.593-32.294,0.593h-32.317v145.064C972.249,1038.604,965.19,1044.451,955.22,1044.451z M1006.911,817.121
|
||||
c25.246,0,45.804-2.331,59.34-7.057c18.793-7.035,30.54-26.409,30.54-52.852c0-31.133-9.415-48.149-29.38-55.207
|
||||
c-11.747-4.135-29.96-5.306-52.875-5.306h-22.915c-14.104,0-19.373,6.464-19.373,19.396v101.025H1006.911z M1328.826,1044.451
|
||||
h-49.309c-9.416,0-15.277-5.268-15.277-17.631v-380.64c0-10.564,4.69-17.622,15.844-17.622h50.531
|
||||
c11.143,0,14.671,6.479,14.671,17.054v148.025h160.958V646.181c0-10.564,4.69-17.622,16.485-17.622h49.89
|
||||
c11.167,0,14.671,6.479,14.671,17.054v381.207c0,11.192-6.43,17.631-16.424,17.631h-48.766c-9.391,0-15.857-5.268-15.857-17.631
|
||||
V864.701h-160.958v162.119C1345.287,1038.013,1338.821,1044.451,1328.826,1044.451z M1746.508,1043.885
|
||||
c-41.128,0-62.857-16.448-62.857-62.856V693.775c0-44.066,19.978-64.624,62.857-64.624h176.197c11.191,0,16.473,3.541,16.473,15.869
|
||||
v41.116c0,9.97-5.281,13.513-16.473,13.513h-139.191c-12.339,0-18.806,6.464-18.806,21.74v72.25h143.337
|
||||
c10.574,0,16.423,3.516,16.423,15.832v41.152c0,9.983-5.849,14.079-16.423,14.079h-143.337v85.761
|
||||
c0,15.855,5.887,22.916,22.323,22.916h145.706c10.551,0,16.437,2.924,16.437,15.843v40.55c0,10.007-5.318,14.114-16.437,14.114
|
||||
H1746.508z M2091.141,1044.451h-49.321c-9.416,0-15.265-5.268-15.265-17.631v-380.64c0-10.564,4.689-17.622,15.857-17.622h50.505
|
||||
c11.168,0,14.696,6.479,14.696,17.054v381.207C2107.614,1038.013,2101.161,1044.451,2091.141,1044.451z M2271.101,1044.451h-47
|
||||
c-9.392,0-15.264-5.268-15.264-17.631V682.039c0-37.019,14.659-55.231,54.023-55.231h22.321c33.478,0,41.153,14.67,52.876,39.35
|
||||
c94.557,193.856,111.599,233.231,129.812,277.272c-1.764-53.442-1.764-104.567-1.764-293.141c0-12.918,5.885-21.73,17.645-21.73
|
||||
h44.065c11.748,0,15.832,8.231,15.832,19.979v353.64c0,38.759-21.717,45.817-45.792,45.817h-18.227
|
||||
c-34.082,0-44.064-11.76-60.513-45.226c-105.145-215.004-114.522-237.34-132.75-280.787c0.593,40.497,1.173,80.454,1.173,304.838
|
||||
C2287.538,1038.013,2281.084,1044.451,2271.101,1044.451z M2708.651,1044.451h-49.915c-9.415,0-14.091-6.438-14.091-17.631V686.136
|
||||
c0-30.553,7.602-59.329,57.564-59.329h17.634c41.103,0,44.04,10.563,63.993,61.081c40.535,103.95,52.307,135.142,64.029,170.939
|
||||
c12.906-36.98,22.335-65.782,62.871-172.111c16.435-43.448,22.31-59.909,66.966-59.909h19.953c47.619,0,50.543,23.481,50.543,59.329
|
||||
v338.93c0,14.117-5.861,19.385-16.473,19.385h-49.371c-9.366,0-15.239-5.268-15.239-18.792c0-211.462,0.592-258.453,2.356-316.597
|
||||
c-14.709,48.149-26.431,82.775-81.083,226.739c-9.391,24.642-15.831,34.626-36.39,34.626h-9.427
|
||||
c-19.386,0-28.172-9.391-36.414-32.292c-52.271-140.981-65.758-177.407-83.392-229.073c1.765,58.144,1.765,108.651,1.765,318.374
|
||||
C2724.532,1036.827,2720.422,1044.451,2708.651,1044.451z M3207.404,1043.885c-41.114,0-62.833-16.448-62.833-62.856V693.775
|
||||
c0-44.066,19.966-64.624,62.833-64.624h176.223c11.155,0,16.437,3.541,16.437,15.869v41.116c0,9.97-5.281,13.513-16.437,13.513
|
||||
h-139.228c-12.34,0-18.793,6.464-18.793,21.74v72.25h143.336c10.563,0,16.461,3.516,16.461,15.832v41.152
|
||||
c0,9.983-5.898,14.079-16.461,14.079h-143.336v85.761c0,15.855,5.849,22.916,22.321,22.916h145.681
|
||||
c10.551,0,16.475,2.924,16.475,15.843v40.55c0,10.007-5.331,14.114-16.475,14.114H3207.404z M3619.782,1044.451h-49.891
|
||||
c-9.438,0-15.867-5.847-15.867-17.631V699.649h-76.049c-11.131,0-16.462-3.542-16.462-13.513v-41.116
|
||||
c0-12.329,5.331-15.869,16.462-15.869h253.356c11.143,0,16.485,3.998,12.45,15.869l-14.451,41.116
|
||||
c-3.084,9.847-8.34,13.513-19.532,13.513h-73.556v327.171C3636.243,1038.604,3629.208,1044.451,3619.782,1044.451z
|
||||
M3799.841,1025.659c-4.678,14.118-8.194,19.967-19.978,19.967c-11.712,0-50.482-1.175-58.133-2.925
|
||||
c-6.465-1.765-8.206-7.06-8.206-11.748c0-5.294,1.741-12.351,4.097-18.793l115.141-334.847
|
||||
c11.735-34.044,20.534-50.505,62.859-50.505h18.792c39.364,0,45.236,15.289,56.418,48.754l112.783,335.414
|
||||
c2.345,6.453,4.107,12.92,4.107,19.384c0,4.69-2.344,9.984-7.688,12.342c-7.032,2.925-41.09,2.925-58.13,2.925
|
||||
c-11.725,0-15.869-5.849-20.545-19.967l-29.95-88.673h-142.757L3799.841,1025.659z M3953.196,868.257
|
||||
c-32.329-103.395-42.338-136.291-51.714-170.965c-9.416,34.082-19.952,68.138-52.876,170.965H3953.196z M4199.952,1043.885
|
||||
c-39.918,0-61.624-17.657-61.624-59.932V646.181c0-10.564,4.665-17.622,15.807-17.622h50.507c11.191,0,14.709,6.479,14.709,17.054
|
||||
v306.021c0,14.093,5.873,21.744,21.717,21.744h117.781c10.526,0,16.4,2.924,16.4,15.843v40.55c0,10.007-5.874,14.114-16.4,14.114
|
||||
H4199.952z M4481.024,1043.885c-39.969,0-61.688-17.657-61.688-59.932V646.181c0-10.564,4.678-17.622,15.845-17.622h50.555
|
||||
c11.144,0,14.661,6.479,14.661,17.054v306.021c0,14.093,5.886,21.744,21.691,21.744h117.783c10.587,0,16.448,2.924,16.448,15.843
|
||||
v40.55c0,10.007-5.861,14.114-16.448,14.114H4481.024z"/>
|
||||
<g id="Bildmarke_1_">
|
||||
<path fill="#007EC1" d="M880.132,1.214c0,0-798.58,355.603-798.604,355.627C16.053,385.876,0,429.485,0,460.211v375.901v420.176
|
||||
c0,8.18,8.279,14.894,17.004,11.115l414.327-184.39c24.802-12.105,57.551-40.313,57.551-96.531V544.477L835.007,390.22
|
||||
c35.896-16.966,61.266-64.08,61.266-126.321c0-0.123,0.013-210.587,0.013-251.395C896.286,2.474,889.228-2.463,880.132,1.214z"/>
|
||||
<path fill="#00406E" d="M17.004,1267.403l414.327-184.39c24.802-12.105,57.551-40.313,57.551-96.531V544.477
|
||||
c0,0-407.477,183.749-407.453,183.749h0.012C13.807,758.236,0.679,803.053,0,836.112v420.176
|
||||
C0,1264.468,8.279,1271.182,17.004,1267.403z"/>
|
||||
<path fill="#000824" d="M0,836.112c0.679-33.059,13.807-77.876,81.441-107.887h-0.012c-0.024,0,407.453-183.749,407.453-183.749
|
||||
v72.347L81.441,768.269C20.804,791.009,0.679,835.113,0,868.17V836.112z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 6.4 KiB |
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg fill="#000000" height="800px" width="800px" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
viewBox="0 0 24 24" xml:space="preserve">
|
||||
<g id="group">
|
||||
<path d="M24,15.9c0-2.8-1.5-5-3.7-6.1C21.3,8.8,22,7.5,22,6c0-2.8-2.2-5-5-5c-2.1,0-3.8,1.2-4.6,3c0,0,0,0,0,0c-0.1,0-0.3,0-0.4,0
|
||||
c-0.1,0-0.3,0-0.4,0c0,0,0,0,0,0C10.8,2.2,9.1,1,7,1C4.2,1,2,3.2,2,6c0,1.5,0.7,2.8,1.7,3.8C1.5,10.9,0,13.2,0,15.9V20h5v3h14v-3h5
|
||||
V15.9z M17,3c1.7,0,3,1.3,3,3c0,1.6-1.3,3-3,3c0-1.9-1.1-3.5-2.7-4.4c0,0,0,0,0,0C14.8,3.6,15.8,3,17,3z M13.4,4.2
|
||||
C13.4,4.2,13.4,4.2,13.4,4.2C13.4,4.2,13.4,4.2,13.4,4.2z M15,9c0,1.7-1.3,3-3,3s-3-1.3-3-3s1.3-3,3-3S15,7.3,15,9z M10.6,4.2
|
||||
C10.6,4.2,10.6,4.2,10.6,4.2C10.6,4.2,10.6,4.2,10.6,4.2z M7,3c1.2,0,2.2,0.6,2.7,1.6C8.1,5.5,7,7.1,7,9C5.3,9,4,7.7,4,6S5.3,3,7,3
|
||||
z M5.1,18H2v-2.1C2,13.1,4.1,11,7,11v0c0,0,0,0,0,0c0.1,0,0.2,0,0.3,0c0,0,0,0,0,0c0.3,0.7,0.8,1.3,1.3,1.8
|
||||
C6.7,13.8,5.4,15.7,5.1,18z M17,21H7v-2.1c0-2.8,2.2-4.9,5-4.9c2.9,0,5,2.1,5,4.9V21z M22,18h-3.1c-0.3-2.3-1.7-4.2-3.7-5.2
|
||||
c0.6-0.5,1-1.1,1.3-1.8c0.1,0,0.2,0,0.4,0v0c2.9,0,5,2.1,5,4.9V18z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6 12H18M12 6V18" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 337 B |
@@ -0,0 +1,41 @@
|
||||
import { useEffect, useCallback } from 'react'
|
||||
import { useSelector, useDispatch } from 'react-redux'
|
||||
import '../styles/errorpopup.css'
|
||||
|
||||
import { exerciseError } from '../store/exerciseSlice'
|
||||
import { participantError } from '../store/participantSlice';
|
||||
import { partyDataError } from '../store/partySlice';
|
||||
|
||||
export function ErrorPopup() {
|
||||
const message = useSelector((state) => state.participants.errorMessage || state.exercise.errorMessage || state.party.errorMessage)
|
||||
const isError = useSelector((state) => state.participants.isError || state.exercise.isError || state.party.isError)
|
||||
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const resetStates = useCallback(() => {
|
||||
dispatch(exerciseError(''));
|
||||
dispatch(participantError(''));
|
||||
dispatch(partyDataError(''));
|
||||
}, [dispatch]);
|
||||
|
||||
useEffect(() => {
|
||||
let timer;
|
||||
if (isError) {
|
||||
timer = setTimeout(() => {
|
||||
resetStates();
|
||||
}, 5000); // Setzt den Timer auf 5 Sekunden
|
||||
}
|
||||
return () => clearTimeout(timer); // Bereinigt den Timer, wenn die Komponente unmountet wird oder isError sich ändert
|
||||
}, [isError, dispatch, resetStates]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{isError &&
|
||||
<div className="popup-error">
|
||||
<button className="popup-error-button" onClick={() => resetStates()}>X</button>
|
||||
<p>{message}</p>
|
||||
</div>
|
||||
}
|
||||
</>
|
||||
)
|
||||
}
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
import React, { useState } from "react";
|
||||
import { MapContainer, TileLayer, FeatureGroup, Marker, Tooltip, Polyline } from 'react-leaflet';
|
||||
import { EditControl } from "react-leaflet-draw";
|
||||
import { connect, useDispatch } from "react-redux";
|
||||
import L from 'leaflet';
|
||||
|
||||
import MapEvents from '../helper/mapEvent.js';
|
||||
|
||||
|
||||
import '../styles/map.css';
|
||||
import 'leaflet/dist/leaflet.css';
|
||||
import 'leaflet-draw/dist/leaflet.draw.css';
|
||||
|
||||
import icon from 'leaflet/dist/images/marker-icon.png';
|
||||
import iconShadow from 'leaflet/dist/images/marker-shadow.png';
|
||||
import { setWalkingPath } from "../store/participantSlice.js";
|
||||
import { completedDrawing } from "./participantList.js";
|
||||
import { addLayers, deleteLayers } from "../store/layerSlice.js";
|
||||
|
||||
let DefaultIcon = L.icon({
|
||||
iconUrl: icon,
|
||||
shadowUrl: iconShadow,
|
||||
iconSize: [25, 41],
|
||||
iconAnchor: [12.5, 41]
|
||||
});
|
||||
|
||||
L.Marker.prototype.options.icon = DefaultIcon;
|
||||
|
||||
function MapBox({ mapPosition, participants, currentParticipant, layers }) {
|
||||
const [position, setPosition] = useState({ lat: 0, lng: 0 });
|
||||
const [clickPosition, setClickPosition] = useState({ lat: 0, lng: 0 });
|
||||
const dispatch = useDispatch();
|
||||
|
||||
return (
|
||||
<div>
|
||||
|
||||
<p className="map-position-center">Center : {position.lat.toFixed(3)} | {position.lng.toFixed(3)}</p>
|
||||
<p className="map-position-click">Clicked : {clickPosition.lat.toFixed(3)} | {clickPosition.lng.toFixed(3)}</p>
|
||||
|
||||
<MapContainer
|
||||
key={mapPosition.toString()}
|
||||
center={mapPosition}
|
||||
zoom={14.5}
|
||||
scrollWheelZoom={true}
|
||||
attributionControl={false}
|
||||
>
|
||||
<TileLayer
|
||||
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
|
||||
/>
|
||||
<FeatureGroup>
|
||||
<EditControl key={currentParticipant}
|
||||
onCreated={(e) => {
|
||||
if (e.layerType === 'polyline') {
|
||||
localStorage.setItem(currentParticipant, true)
|
||||
dispatch(deleteLayers(currentParticipant))
|
||||
dispatch(setWalkingPath({ equipmentNumber: currentParticipant, path: JSON.parse(JSON.stringify(e.layer.getLatLngs())) }))
|
||||
completedDrawing()
|
||||
|
||||
var newObject = {
|
||||
layers: [],
|
||||
participant: currentParticipant
|
||||
}
|
||||
newObject.layers.push(JSON.parse(JSON.stringify(e.layer.getLatLngs())))
|
||||
|
||||
dispatch(addLayers(newObject))
|
||||
e.layer.remove()
|
||||
}
|
||||
}}
|
||||
position='topright'
|
||||
edit={{
|
||||
edit: false,
|
||||
remove: false,
|
||||
}}
|
||||
draw={{
|
||||
rectangle: false,
|
||||
circle: false,
|
||||
circlemarker: false,
|
||||
polygon: false
|
||||
}}
|
||||
/>
|
||||
</FeatureGroup>
|
||||
{/* Display the participants on the map */}
|
||||
{participants.map((participant, i) => (
|
||||
<Marker key={i} position={[participant.currentPosition[0], participant.currentPosition[1]]}>
|
||||
<Tooltip>{participant.equipmentNumber}</Tooltip>
|
||||
</Marker>
|
||||
))}
|
||||
{layers.map((layer, i) => (
|
||||
<Polyline key={i} positions={layer.layers} />
|
||||
))}
|
||||
<MapEvents setPosition={setPosition} mapPosition={mapPosition} setClickPosition={setClickPosition} />
|
||||
</MapContainer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
return {
|
||||
mapPosition: state.map,
|
||||
participants: state.participants.data,
|
||||
currentParticipant: state.participants.currentParticipantWalkPathEq,
|
||||
layers: state.layer
|
||||
};
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps)(MapBox);
|
||||
@@ -0,0 +1,134 @@
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import "../styles/participantList.css"
|
||||
import logo from "../assets/group-svgrepo-com.svg"
|
||||
|
||||
import { useDispatch } from 'react-redux'
|
||||
import { changeMapPosition } from '../store/mapSlice';
|
||||
import { useSetAbcProtection, select, setCurrentWalkParticipant } from '../store/participantSlice';
|
||||
import { useCalculation } from '../helper/walkingPathCaclulation';
|
||||
import { sleep } from '../helper/sleep';
|
||||
import { changeHoldingPathValue } from './sideBar';
|
||||
|
||||
let isFinishedDrawing = false;
|
||||
export function completedDrawing() {
|
||||
isFinishedDrawing = true;
|
||||
}
|
||||
export function startDrawing() {
|
||||
isFinishedDrawing = false;
|
||||
}
|
||||
export default function ParticipantList(participant) {
|
||||
const [buttonState, setButtonState] = useState(false);
|
||||
const calculate = useCalculation()
|
||||
const dispatch = useDispatch()
|
||||
// Handels value of checkbox input
|
||||
const useHandleChange = (e) => {
|
||||
let checkStatus = participant.participant.abcProtection
|
||||
dispatch(useSetAbcProtection({ equipmentNumber: participant.participant.equipmentNumber, newAbcProtection: !checkStatus }))
|
||||
};
|
||||
|
||||
// Opens Collapsible
|
||||
function buttonPressed() {
|
||||
setButtonState(!buttonState);
|
||||
}
|
||||
|
||||
async function drawingPath() {
|
||||
dispatch(setCurrentWalkParticipant({ currentParticipantWalkPathEq: participant.participant.equipmentNumber }))
|
||||
let drawButton = document.getElementsByClassName("leaflet-draw-draw-polyline")[0];
|
||||
|
||||
await sleep(10)
|
||||
|
||||
drawButton.click()
|
||||
}
|
||||
|
||||
function patrouille() {
|
||||
localStorage.setItem(participant.participant.equipmentNumber.toString(), false)
|
||||
calculate(participant.participant.path, participant.participant.equipmentNumber, participant.participant, "Patrouille")
|
||||
startDrawing()
|
||||
}
|
||||
|
||||
function oneTimeUsePath() {
|
||||
localStorage.setItem(participant.participant.equipmentNumber.toString(), false)
|
||||
calculate(participant.participant.path, participant.participant.equipmentNumber, participant.participant, "OTUP")
|
||||
startDrawing()
|
||||
}
|
||||
|
||||
function hold() {
|
||||
localStorage.setItem(participant.participant.equipmentNumber.toString(), false)
|
||||
calculate(participant.participant.path, participant.participant.equipmentNumber, participant.participant, "Hold")
|
||||
startDrawing()
|
||||
changeHoldingPathValue(true)
|
||||
}
|
||||
return (
|
||||
<div className='collapsible-div'>
|
||||
|
||||
<button className={buttonState ? 'collapsible-active' : 'collapsible'} onClick={buttonPressed}>
|
||||
<img src={logo} alt="Group" className='group-icon' />
|
||||
<div className='participant-text'>
|
||||
<p style={{ color: participant.participant.party }} > {participant.participant.equipmentNumber} | {participant.participant.type}</p>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
{buttonState &&
|
||||
<div className="content">
|
||||
{/* Table of Participants Information */}
|
||||
<table className='participants-table'>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<h4>Group:</h4>
|
||||
</td>
|
||||
<td>{participant.participant.party}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<h4>Typ:</h4>
|
||||
</td>
|
||||
<td>{participant.participant.type}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<h4>Equip. No.:</h4>
|
||||
</td>
|
||||
<td>{participant.participant.equipmentNumber}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<h4>Position:</h4>
|
||||
</td>
|
||||
<td>{participant.participant.currentPosition[0].toFixed(3)} | {participant.participant.currentPosition[1].toFixed(3)}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<h4>ABC Protection:</h4>
|
||||
</td>
|
||||
<td><input type="checkbox" value={participant.participant.abcProtection} onChange={useHandleChange}></input></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<h4>Status:</h4>
|
||||
</td>
|
||||
<td>{participant.participant.status}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
{/* Buttons for the Participants */}
|
||||
<div className='collapsible-button-div'>
|
||||
|
||||
<button onClick={() => dispatch(changeMapPosition(participant.participant.currentPosition))} className="collapsible-button">GO TO</button>
|
||||
<button className='collapsible-button' onClick={() => dispatch(select({ participant: participant.participant }))}>SET POS</button>
|
||||
<button className='collapsible-button' onClick={() => drawingPath()}>EMULATE RUN</button>
|
||||
{isFinishedDrawing && (
|
||||
<div className='choose-popup'>
|
||||
<button className='collapsible-button' onClick={() => patrouille()}>Patrouille</button>
|
||||
<button className='collapsible-button' onClick={() => oneTimeUsePath()}>One time use path</button>
|
||||
<button className='collapsible-button' onClick={() => hold()}>Hold Path</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
+123
@@ -0,0 +1,123 @@
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
|
||||
import { useSelector, useDispatch } from 'react-redux'
|
||||
|
||||
import { useCheckIfParticipantIsValid } from '../websocket/checkIfParticipantIsValid'
|
||||
import "../styles/popup.css"
|
||||
import { useGetPartyData } from '../websocket/getPartyData';
|
||||
import { clearPartyData } from '../store/partySlice';
|
||||
import { setCurrentExerciseId } from '../store/exerciseSlice';
|
||||
|
||||
export default function Popup({ createPopupState, setPopupState }) {
|
||||
const [equipmentNumber, setEquipmentNumber] = useState('');
|
||||
const [selectedExerciseId, setSelectedExerciseId] = useState('');
|
||||
const [isExerciseIdSelected, setIsExerciseIdSelected] = useState(false);
|
||||
const [partyId, setPartyId] = useState(2);
|
||||
const partyList = useSelector((state) => state.party.partyData);
|
||||
const dispatch = useDispatch()
|
||||
|
||||
const excerciseIds = useSelector((state) => state.exercise.exerciseIds);
|
||||
// Add participant to the store
|
||||
function useHandleClick() {
|
||||
useCheckIfParticipantIsValid(equipmentNumber, selectedExerciseId, dispatch, partyList, partyId);
|
||||
dispatch(clearPartyData([]))
|
||||
setIsExerciseIdSelected(false)
|
||||
}
|
||||
function useHandleExerciseIdSubmit() {
|
||||
useGetPartyData(selectedExerciseId, dispatch)
|
||||
setIsExerciseIdSelected(!isExerciseIdSelected)
|
||||
setPartyId(2)
|
||||
dispatch(setCurrentExerciseId(selectedExerciseId))
|
||||
setPopupState(false)
|
||||
}
|
||||
function handlePartyNameChange(event) {
|
||||
setPartyId(event.target.value)
|
||||
}
|
||||
// Handle input change
|
||||
const handleInputChange = (event) => {
|
||||
setEquipmentNumber(event.target.value);
|
||||
};
|
||||
// Handle exerciseId change
|
||||
const handleExerciseIdChange = (event) => {
|
||||
setSelectedExerciseId(event.target.value);
|
||||
};
|
||||
// Close popup when ESC key is pressed
|
||||
const handleKeyDown = useCallback((event) => {
|
||||
if (event.keyCode === 27) {
|
||||
setPopupState(false);
|
||||
}
|
||||
}, [setPopupState]);
|
||||
// Add event listener when popup is open
|
||||
useEffect(() => {
|
||||
if (createPopupState) {
|
||||
document.addEventListener("keydown", handleKeyDown);
|
||||
return () => {
|
||||
document.removeEventListener("keydown", handleKeyDown);
|
||||
};
|
||||
}
|
||||
}, [createPopupState, handleKeyDown]);
|
||||
return (
|
||||
<>
|
||||
{createPopupState && (
|
||||
<div className='popup-blur'>
|
||||
<div className="popup">
|
||||
<p className="popup-text">SELECT EXERCISE ID</p>
|
||||
</div>
|
||||
<div className='select-party'>
|
||||
<form className='select-party-form'>
|
||||
<select value={selectedExerciseId} onChange={handleExerciseIdChange} className='select-party-form-select'>
|
||||
{excerciseIds.map((exerciseId) => (
|
||||
<option key={exerciseId} value={exerciseId}>{exerciseId}</option>
|
||||
))}
|
||||
</select>
|
||||
</form>
|
||||
|
||||
<div className="select-party-text">ID :</div>
|
||||
<div className="select-party-dropdown"></div>
|
||||
</div>
|
||||
<div className="popup-buttons">
|
||||
<button className="popup-cancel" onClick={() => setPopupState(false)}>Cancel</button>
|
||||
<button onClick={useHandleExerciseIdSubmit} className="popup-submit">Submit</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{isExerciseIdSelected && (
|
||||
<div className='popup-blur'>
|
||||
|
||||
<div className="popup">
|
||||
<p className="popup-text">ADD A PARTICIPANT</p>
|
||||
</div>
|
||||
|
||||
<div className="select-party">
|
||||
|
||||
<form className='select-party-form'>
|
||||
<select id="group" name="group" className='select-party-form-select' onChange={handlePartyNameChange}>
|
||||
{partyList.map((item, index) => (
|
||||
<option key={index} value={item.id} style={{ color: item.color }}>{item.party}</option>
|
||||
))}
|
||||
</select>
|
||||
</form>
|
||||
|
||||
<div className="select-party-text">Group :</div>
|
||||
<div className="select-party-dropdown"></div>
|
||||
</div>
|
||||
|
||||
<div className='select-number'>
|
||||
|
||||
<input className='select-number-inputfield' type="number" value={equipmentNumber} onChange={handleInputChange}></input>
|
||||
<div className="select-number-text">Number :</div>
|
||||
<div className="select-number-inputfield-div"></div>
|
||||
|
||||
</div>
|
||||
|
||||
<div className="popup-buttons">
|
||||
|
||||
<button className="popup-cancel" onClick={() => setIsExerciseIdSelected(false)}>Cancel</button>
|
||||
<button className="popup-submit" onClick={useHandleClick}>Submit</button>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
+119
@@ -0,0 +1,119 @@
|
||||
import React, { useState } from 'react';
|
||||
import "../styles/sideBar.css";
|
||||
import logo from "../assets/Rheinmetall_Logo_2021.svg";
|
||||
import ParticipantList from './participantList';
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { getAllParticipants } from "../websocket/getAllParticipants";
|
||||
import { setWalkingPath } from '../store/participantSlice';
|
||||
import { generateRandomPath } from '../helper/generateRandomPath';
|
||||
import { useCalculation } from '../helper/walkingPathCaclulation';
|
||||
import { addLayers } from '../store/layerSlice';
|
||||
import { sleep } from '../helper/sleep';
|
||||
import { getStateFromLocalStorage, saveStateToLocalStorage } from '../helper/localStorageUsage';
|
||||
let isHoldingPaths = false;
|
||||
export function changeHoldingPathValue(value) {
|
||||
isHoldingPaths = value;
|
||||
}
|
||||
export default function SideBar({ createPopupStateChanger }) {
|
||||
const [hasParticipants, setHasParticipants] = useState(false)
|
||||
const [startedStressTest, setStartedStressTest] = useState(false)
|
||||
const participantList = useSelector((state) => state.participants.data);
|
||||
const partyList = useSelector(state => state.party.partyData)
|
||||
const excerciseId = useSelector(state => state.exercise.currentExerciseId) ? 0 : 42
|
||||
const calculate = useCalculation()
|
||||
const dispatch = useDispatch();
|
||||
|
||||
function useStartStressTest() {
|
||||
const lowerRight = [53.019, 8.995]
|
||||
const topLeft = [53.128, 8.730]
|
||||
|
||||
for (var participant of participantList) {
|
||||
var newObject = {
|
||||
layers: [],
|
||||
participant: participant
|
||||
}
|
||||
|
||||
var path = generateRandomPath(lowerRight, topLeft, participant.currentPosition);
|
||||
newObject.layers.push(path)
|
||||
|
||||
localStorage.setItem(participant.equipmentNumber.toString(), false)
|
||||
|
||||
dispatch(setWalkingPath({ equipmentNumber: participant.equipmentNumber, path: path }))
|
||||
dispatch(addLayers(newObject))
|
||||
calculate(path, participant.equipmentNumber, participant, "OTUP")
|
||||
}
|
||||
|
||||
|
||||
setHasParticipants(false)
|
||||
setStartedStressTest(true)
|
||||
}
|
||||
|
||||
async function useGetAllParticipants() {
|
||||
getAllParticipants(excerciseId || 42, dispatch, partyList)
|
||||
|
||||
await sleep(500)
|
||||
|
||||
setHasParticipants(true)
|
||||
}
|
||||
|
||||
function startAllPaths() {
|
||||
var pathObjectArray = [];
|
||||
for (var singleParticipant of participantList) {
|
||||
var pathObject = {
|
||||
equipmentNumber: singleParticipant.equipmentNumber,
|
||||
path: singleParticipant.path
|
||||
}
|
||||
|
||||
if (singleParticipant.path && singleParticipant.path.length > 0) {
|
||||
localStorage.setItem(singleParticipant.equipmentNumber.toString(), false)
|
||||
calculate(singleParticipant.path, singleParticipant.equipmentNumber, singleParticipant, "OTUP")
|
||||
pathObjectArray.push(pathObject)
|
||||
}
|
||||
}
|
||||
saveStateToLocalStorage("paths", pathObjectArray)
|
||||
getStateFromLocalStorage("paths")
|
||||
changeHoldingPathValue(false)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="sidebar">
|
||||
|
||||
<div className="sidebar-header">
|
||||
<img src={logo} alt="Rheinmetall Logo" className='logo' />
|
||||
</div>
|
||||
|
||||
<div className="participant-list">
|
||||
<p className="participant-list-text">Participant list</p>
|
||||
<div className="addButton-div" onClick={() => createPopupStateChanger(true)}>
|
||||
<div className="addButton"><p className="addButton-text">+</p></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="sidebar-content">
|
||||
{
|
||||
participantList.map((participant, i) => {
|
||||
return <ParticipantList key={i} participant={participant} participantList={participantList} />
|
||||
})
|
||||
}
|
||||
</div>
|
||||
<div className='sidebar-footer'>
|
||||
<button onClick={useGetAllParticipants}>Get all Participants</button>
|
||||
{hasParticipants &&
|
||||
<button onClick={useStartStressTest}>Start Stress Test</button>
|
||||
}
|
||||
{startedStressTest &&
|
||||
<div className='sidebar-startStop-container'>
|
||||
<button>Start</button>
|
||||
<button>Pause</button>
|
||||
<button>Stop</button>
|
||||
</div>
|
||||
}
|
||||
{isHoldingPaths > 0 && (
|
||||
<div className='startingpath-popup'>
|
||||
<button className='collapsible-button' onClick={() => startAllPaths()}>Start all Paths</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
+126
@@ -0,0 +1,126 @@
|
||||
export const participantData = [
|
||||
{
|
||||
equipmentNumber: 1054,
|
||||
currentPosition: [53.0580229, 8.8972232],
|
||||
abcProtection: true,
|
||||
gear: [
|
||||
{
|
||||
weapon: "AK-74",
|
||||
munition: "7.62x39mm AP"
|
||||
}, {
|
||||
weapon: "AK-74",
|
||||
munition: "7.62x39mm AP"
|
||||
}
|
||||
],
|
||||
party: "Blue",
|
||||
symbol: "--",
|
||||
type: "Soldat/G",
|
||||
status: "Wounded",
|
||||
selected: false,
|
||||
walkingPath: []
|
||||
},
|
||||
{
|
||||
equipmentNumber: 1024,
|
||||
currentPosition: [53.0510229, 8.8902232],
|
||||
abcProtection: true,
|
||||
gear: [
|
||||
{
|
||||
weapon: "AK-74",
|
||||
munition: "7.62x39mm AP"
|
||||
}, {
|
||||
weapon: "AK-74",
|
||||
munition: "7.62x39mm AP"
|
||||
}
|
||||
],
|
||||
party: "Blue",
|
||||
symbol: "--",
|
||||
type: "Soldat/G",
|
||||
status: "Wounded",
|
||||
selected: false,
|
||||
walkingPath: []
|
||||
},
|
||||
{
|
||||
equipmentNumber: 1014,
|
||||
currentPosition: [53.0520229, 8.8932232],
|
||||
abcProtection: true,
|
||||
gear: [
|
||||
{
|
||||
weapon: "AK-74",
|
||||
munition: "7.62x39mm AP"
|
||||
}, {
|
||||
weapon: "AK-74",
|
||||
munition: "7.62x39mm AP"
|
||||
}
|
||||
],
|
||||
party: "Red",
|
||||
symbol: "--",
|
||||
type: "Soldat/G",
|
||||
status: "Wounded",
|
||||
selected: false,
|
||||
walkingPath: [],
|
||||
walkMode: ""
|
||||
},
|
||||
{
|
||||
equipmentNumber: 1088,
|
||||
currentPosition: [53.0550229, 8.8942232],
|
||||
abcProtection: true,
|
||||
gear: [
|
||||
{
|
||||
weapon: "AK-74",
|
||||
munition: "7.62x39mm AP"
|
||||
}, {
|
||||
weapon: "AK-74",
|
||||
munition: "7.62x39mm AP"
|
||||
}
|
||||
],
|
||||
party: "Pink",
|
||||
symbol: "--",
|
||||
type: "Soldat/G",
|
||||
status: "Wounded",
|
||||
selected: false,
|
||||
walkingPath: [],
|
||||
walkMode: ""
|
||||
|
||||
},
|
||||
{
|
||||
equipmentNumber: 1034,
|
||||
currentPosition: [53.0540229, 8.8972232],
|
||||
abcProtection: true,
|
||||
gear: [
|
||||
{
|
||||
weapon: "AK-74",
|
||||
munition: "7.62x39mm AP"
|
||||
}, {
|
||||
weapon: "AK-74",
|
||||
munition: "7.62x39mm AP"
|
||||
}
|
||||
],
|
||||
party: "Orange",
|
||||
symbol: "--",
|
||||
type: "Soldat/G",
|
||||
status: "Wounded",
|
||||
selected: false,
|
||||
walkingPath: [],
|
||||
walkMode: ""
|
||||
|
||||
},
|
||||
]
|
||||
|
||||
export const partyData = [
|
||||
{
|
||||
id:2,
|
||||
party:"RED",
|
||||
color:"#eb0016"
|
||||
},
|
||||
{
|
||||
id:3,
|
||||
party:"BLUE",
|
||||
color:"#1721a6"
|
||||
},
|
||||
{
|
||||
id:4,
|
||||
party:"GREEN",
|
||||
color:"#00ff00"
|
||||
}
|
||||
]
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
export function generateRandomPath(lowerRight, topLeft,currentPosition) {
|
||||
const path = [
|
||||
{
|
||||
lat: currentPosition[0],
|
||||
lng: currentPosition[1],
|
||||
},
|
||||
{
|
||||
lat: Math.random() * (topLeft[0] - lowerRight[0]) + lowerRight[0],
|
||||
lng: Math.random() * (topLeft[1] - lowerRight[1]) + lowerRight[1],
|
||||
},
|
||||
|
||||
{
|
||||
lat: Math.random() * (topLeft[0] - lowerRight[0]) + lowerRight[0],
|
||||
lng: Math.random() * (topLeft[1] - lowerRight[1]) + lowerRight[1],
|
||||
},
|
||||
]
|
||||
return path;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
export function saveStateToLocalStorage(item, pathObject) {
|
||||
localStorage.setItem(
|
||||
item,
|
||||
JSON.stringify(pathObject)
|
||||
);
|
||||
}
|
||||
|
||||
export function getStateFromLocalStorage(item) {
|
||||
let data = localStorage.getItem(item);
|
||||
if (data !== undefined) {
|
||||
console.log(data);
|
||||
}
|
||||
};
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useMap } from 'react-leaflet';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
|
||||
import { useSetPosition as SetParticipantPosition, deselect } from '../store/participantSlice';
|
||||
|
||||
|
||||
|
||||
export default function MapEvents({ setPosition, mapPosition, setClickPosition }) {
|
||||
const dispatch = useDispatch()
|
||||
const map = useMap();
|
||||
const currentParticipant = useSelector((state) => state.participants.currentParticipant)
|
||||
|
||||
// Set the positions
|
||||
useEffect(() => {
|
||||
if (map) {
|
||||
const onMove = () => {
|
||||
setPosition(map.getCenter());
|
||||
};
|
||||
const onClick = (e) => {
|
||||
if (currentParticipant === "") {
|
||||
return
|
||||
}
|
||||
|
||||
setClickPosition(e.latlng);
|
||||
|
||||
dispatch(SetParticipantPosition({ equipmentNumber: currentParticipant.equipmentNumber, newPosition: [e.latlng.lat, e.latlng.lng], exerciseId: currentParticipant.exerciseId }))
|
||||
dispatch(deselect({ currentParticipant: "" }))
|
||||
|
||||
};
|
||||
|
||||
map.on('move', onMove);
|
||||
map.on('click', onClick);
|
||||
|
||||
return () => {
|
||||
map.off('move', onMove);
|
||||
map.off('click', onClick);
|
||||
};
|
||||
}
|
||||
}, [dispatch, map, mapPosition, setClickPosition, setPosition, currentParticipant]);
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export function sleep(ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
import { useSetPosition as setPosition, walkingState } from "../store/participantSlice";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { sleep } from "./sleep";
|
||||
|
||||
export function useCalculation() {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
return async function (latLngs, equipmentNumber, participant, walkMode) {
|
||||
var speed = 0.00003;
|
||||
var currentPosition = participant.currentPosition;
|
||||
|
||||
if (latLngs.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
while (localStorage.getItem(equipmentNumber.toString()) === "false") {
|
||||
if (walkMode === "Patrouille") {
|
||||
for (var i = 0; i < latLngs.length; i++) {
|
||||
currentPosition = await moveToTarget(latLngs[i], currentPosition, speed, dispatch, equipmentNumber);
|
||||
}
|
||||
// Rückwärts durch den Pfad
|
||||
for (i = latLngs.length - 1; i >= 0; i--) {
|
||||
currentPosition = await moveToTarget(latLngs[i], currentPosition, speed, dispatch, equipmentNumber);
|
||||
}
|
||||
} else if (walkMode === "OTUP") {
|
||||
for (i = 0; i < latLngs.length; i++) {
|
||||
currentPosition = await moveToTarget(latLngs[i], currentPosition, speed, dispatch, equipmentNumber);
|
||||
}
|
||||
break;
|
||||
} else if (walkMode === "Hold") {
|
||||
break;
|
||||
}
|
||||
}
|
||||
dispatch(walkingState({ equipmentNumber: equipmentNumber, isWalking: false, walkingMode: " " }))
|
||||
}
|
||||
}
|
||||
|
||||
async function moveToTarget(target, currentPosition, speed, dispatch, equipmentNumber) {
|
||||
var targetLat = target.lat;
|
||||
var targetLng = target.lng;
|
||||
|
||||
while (localStorage.getItem(equipmentNumber.toString()) === "false") {
|
||||
var diffLat = targetLat - currentPosition[0];
|
||||
var diffLng = targetLng - currentPosition[1];
|
||||
|
||||
var distance = Math.sqrt(diffLat * diffLat + diffLng * diffLng);
|
||||
|
||||
if (distance < speed) {
|
||||
currentPosition = [targetLat, targetLng];
|
||||
break;
|
||||
}
|
||||
// Berechne den Anteil der Bewegung in Richtung des Ziels
|
||||
var ratio = speed / distance;
|
||||
var moveLat = diffLat * ratio;
|
||||
var moveLng = diffLng * ratio;
|
||||
// Aktualisiere die aktuelle Position
|
||||
currentPosition = [currentPosition[0] + moveLat, currentPosition[1] + moveLng];
|
||||
dispatch(setPosition({ equipmentNumber: equipmentNumber, newPosition: currentPosition, exerciseId: 42 }));
|
||||
// Warte eine Sekunde bevor die Position erneut aktualisiert wird
|
||||
await sleep(300);
|
||||
}
|
||||
return currentPosition;
|
||||
}
|
||||
Whitespace-only changes.
+42
@@ -0,0 +1,42 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import { createBrowserRouter, RouterProvider } from "react-router-dom";
|
||||
import './index.css';
|
||||
import App from './App';
|
||||
import reportWebVitals from './reportWebVitals';
|
||||
import { configureStore } from "@reduxjs/toolkit";
|
||||
import { Provider } from "react-redux";
|
||||
|
||||
import participantReducer from './store/participantSlice'
|
||||
import mapReducer from './store/mapSlice'
|
||||
import partyReducer from './store/partySlice'
|
||||
import exerciseReducer from './store/exerciseSlice'
|
||||
import layerReducer from './store/layerSlice'
|
||||
|
||||
const router = createBrowserRouter([
|
||||
{
|
||||
path: "/",
|
||||
element: <App />,
|
||||
}
|
||||
]);
|
||||
|
||||
const store = configureStore({
|
||||
reducer: {
|
||||
participants: participantReducer,
|
||||
map: mapReducer,
|
||||
party: partyReducer,
|
||||
exercise: exerciseReducer,
|
||||
layer: layerReducer,
|
||||
}
|
||||
});
|
||||
|
||||
const root = ReactDOM.createRoot(document.getElementById('root'));
|
||||
root.render(
|
||||
<React.StrictMode>
|
||||
<Provider store={store}>
|
||||
<RouterProvider router={router} />
|
||||
</Provider>
|
||||
</React.StrictMode>
|
||||
);
|
||||
|
||||
reportWebVitals();
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
const reportWebVitals = onPerfEntry => {
|
||||
if (onPerfEntry && onPerfEntry instanceof Function) {
|
||||
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
|
||||
getCLS(onPerfEntry);
|
||||
getFID(onPerfEntry);
|
||||
getFCP(onPerfEntry);
|
||||
getLCP(onPerfEntry);
|
||||
getTTFB(onPerfEntry);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export default reportWebVitals;
|
||||
@@ -0,0 +1,25 @@
|
||||
import { createSlice } from '@reduxjs/toolkit'
|
||||
|
||||
const excerciseId = createSlice({
|
||||
name: "exercise",
|
||||
initialState: {exerciseIds : [], currentExerciseId: 0, isError : false, errorMessage : "" },
|
||||
reducers: {
|
||||
setExcerciseId: (state, action) => {
|
||||
for(var excerciseId in action.payload) {
|
||||
state.exerciseIds[excerciseId] = action.payload[excerciseId]
|
||||
}
|
||||
},
|
||||
setCurrentExerciseId: (state, action) => {
|
||||
state.currentExerciseId = action.payload
|
||||
},
|
||||
exerciseError: (state, action) => {
|
||||
state.isError = action.payload.isError;
|
||||
state.errorMessage = action.payload.errorMessage;
|
||||
}
|
||||
|
||||
}
|
||||
})
|
||||
|
||||
export const { setExcerciseId, setCurrentExerciseId, exerciseError } = excerciseId.actions
|
||||
|
||||
export default excerciseId.reducer
|
||||
@@ -0,0 +1,22 @@
|
||||
import { createSlice } from "@reduxjs/toolkit";
|
||||
|
||||
const layerSlice = createSlice({
|
||||
name: "layer",
|
||||
initialState: [],
|
||||
reducers: {
|
||||
addLayers: (state, action) => {
|
||||
state.push(action.payload)
|
||||
},
|
||||
deleteLayers: (state, action) => {
|
||||
for(var layerIndex in state) {
|
||||
if (state[layerIndex].participant === action.payload) {
|
||||
state.splice(layerIndex, 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
export const { addLayers, deleteLayers } = layerSlice.actions
|
||||
|
||||
export default layerSlice.reducer
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
import { createSlice } from "@reduxjs/toolkit";
|
||||
|
||||
const mapSlice = createSlice({
|
||||
name: "map",
|
||||
initialState: [53.0580229, 8.8972232],
|
||||
reducers: {
|
||||
changeMapPosition: (state, action) => {
|
||||
state[0] = action.payload[0]
|
||||
state[1] = action.payload[1]
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
export const { changeMapPosition } = mapSlice.actions
|
||||
|
||||
export default mapSlice.reducer
|
||||
@@ -0,0 +1,86 @@
|
||||
import { createSlice } from "@reduxjs/toolkit";
|
||||
import { original } from 'immer'
|
||||
import { useSendPositionChange } from "../websocket/sendPositionChange";
|
||||
import { useChangeAbcProtection } from "../websocket/changeAbcProtection"
|
||||
import { useDispatch as dispatch } from "react-redux";
|
||||
|
||||
const participantSlice = createSlice({
|
||||
name: "participants",
|
||||
initialState: { data: [], currentParticipant: "", currentParticipantWalkPathEq: 0, isError: false, errorMessage: "" },
|
||||
reducers: {
|
||||
useSetPosition: (state, action) => {
|
||||
for (var i = 0; i < original(state.data).length; i++) {
|
||||
if (original(state.data)[i].equipmentNumber === action.payload.equipmentNumber) {
|
||||
state.data[i].currentPosition = action.payload.newPosition
|
||||
}
|
||||
}
|
||||
useSendPositionChange(action.payload.equipmentNumber, action.payload.newPosition, action.payload.exerciseId ,dispatch)
|
||||
},
|
||||
useSetAbcProtection: (state, action) => {
|
||||
var exerciseId = 0;
|
||||
for (var i = 0; i < original(state.data).length; i++) {
|
||||
if (original(state.data)[i].equipmentNumber === action.payload.equipmentNumber) {
|
||||
state.data[i].abcProtection = action.payload.newAbcProtection
|
||||
exerciseId = state.data[i].exerciseId;
|
||||
}
|
||||
}
|
||||
useChangeAbcProtection(action.payload.equipmentNumber, action.payload.newAbcProtection, exerciseId, dispatch)
|
||||
},
|
||||
select: (state, action) => {
|
||||
for (var i = 0; i < original(state.data).length; i++) {
|
||||
if (original(state.data)[i].equipmentNumber === action.payload.participant.equipmentNumber) {
|
||||
state.currentParticipant = action.payload.participant;
|
||||
}
|
||||
}
|
||||
},
|
||||
deselect: (state, action) => {
|
||||
state.currentParticipant = "";
|
||||
},
|
||||
addParticipant: (state, action) => {
|
||||
for (var i = 0; i < original(state.data).length; i++) {
|
||||
if (original(state.data)[i].equipmentNumber === action.payload.equipmentNumber) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
state.data.push(action.payload)
|
||||
},
|
||||
setCurrentWalkParticipant: (state, action) => {
|
||||
state.currentParticipantWalkPathEq = action.payload.currentParticipantWalkPathEq
|
||||
},
|
||||
setWalkingPath: (state, action) => {
|
||||
for (var i = 0; i < original(state.data).length; i++) {
|
||||
if (original(state.data)[i].equipmentNumber === action.payload.equipmentNumber) {
|
||||
state.data[i].path = action.payload.path
|
||||
}
|
||||
}
|
||||
},
|
||||
walkingState: (state, action) => {
|
||||
for (var i = 0; i < original(state.data).length; i++) {
|
||||
if (original(state.data)[i].equipmentNumber === action.payload.equipmentNumber) {
|
||||
state.data[i].isWalking = action.payload.isWalking
|
||||
state.data[i].walkingMode = action.payload.walkingMode
|
||||
}
|
||||
}
|
||||
|
||||
},
|
||||
participantError: (state, action) => {
|
||||
state.isError = action.payload.isError;
|
||||
state.errorMessage = action.payload.errorMessage;
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
export const {
|
||||
useSetPosition,
|
||||
useSetAbcProtection,
|
||||
select,
|
||||
deselect,
|
||||
addParticipant,
|
||||
setCurrentWalkParticipant,
|
||||
setWalkingPath,
|
||||
walkingState,
|
||||
participantError,
|
||||
} = participantSlice.actions
|
||||
|
||||
export default participantSlice.reducer
|
||||
@@ -0,0 +1,33 @@
|
||||
import { createSlice } from "@reduxjs/toolkit";
|
||||
|
||||
const partySlice = createSlice({
|
||||
name: "party",
|
||||
initialState: { partyData: [], isError: false, errorMessage: "" },
|
||||
reducers: {
|
||||
setPartyData: (state, action) => {
|
||||
var found = false;
|
||||
|
||||
for(var partyObject of state.partyData) {
|
||||
if (partyObject["party"] === action.payload["party"]) {
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
|
||||
if(!found) {
|
||||
state.partyData.push(action.payload)
|
||||
}
|
||||
},
|
||||
clearPartyData: (state, action) => {
|
||||
state = action.payload
|
||||
},
|
||||
partyDataError: (state, action) => {
|
||||
state.isError = action.payload.isError;
|
||||
state.errorMessage = action.payload.errorMessage;
|
||||
},
|
||||
|
||||
}
|
||||
})
|
||||
|
||||
export const { setPartyData, clearPartyData, partyDataError } = partySlice.actions
|
||||
|
||||
export default partySlice.reducer
|
||||
@@ -0,0 +1,38 @@
|
||||
.popup-error {
|
||||
position: absolute;
|
||||
width: 40%;
|
||||
height: 20px;
|
||||
top: 5%;
|
||||
left: 50%;
|
||||
background-color: white;
|
||||
color: red;
|
||||
z-index: 3;
|
||||
border-radius: 25px;
|
||||
transform: translate(-50%, -50%);
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
.popup-error-button {
|
||||
position: absolute;
|
||||
left: 95%;
|
||||
border-radius: 25px;
|
||||
border: 1px solid #007EC1;
|
||||
background-color: #007EC1;
|
||||
color: #ffffff;
|
||||
padding: 5px 10px;
|
||||
}
|
||||
|
||||
.popup-error-button:hover {
|
||||
border: 1px solid #0073b1;
|
||||
background-color: #0073b1;
|
||||
color: #ffffff;
|
||||
padding: 5px 10px;
|
||||
}
|
||||
|
||||
.popup-error-button:disabled,
|
||||
.popup-error-button[disabled]{
|
||||
border: 1px solid #007EC1;
|
||||
background-color: #007EC1;
|
||||
color: #666666;
|
||||
}
|
||||
|
||||
+121
@@ -0,0 +1,121 @@
|
||||
.leaflet-container {
|
||||
position: relative;
|
||||
width: 80%;
|
||||
height: 100vh;
|
||||
margin-right: 0;
|
||||
margin-left: auto;
|
||||
outline-style: none;
|
||||
}
|
||||
|
||||
.leaflet-pane,
|
||||
.leaflet-control,
|
||||
.leaflet-top,
|
||||
.leaflet-bottom {
|
||||
z-index: 0 !important;
|
||||
}
|
||||
|
||||
.map-position-center {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
z-index: 1;
|
||||
margin: 10px;
|
||||
padding: 10px;
|
||||
border-radius: 15px;
|
||||
background-color: white;
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.map-position-click {
|
||||
position: absolute;
|
||||
top: 48px;
|
||||
right: 10px;
|
||||
z-index: 1;
|
||||
margin: 10px;
|
||||
padding: 10px;
|
||||
border-radius: 15px;
|
||||
background-color: white;
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1100px) {
|
||||
.leaflet-container {
|
||||
width: 60%;
|
||||
height: 100vh;
|
||||
}
|
||||
}
|
||||
|
||||
/* Media queries for responsive design */
|
||||
@media only screen and (max-width: 1024px) {
|
||||
.leaflet-container {
|
||||
width: 60%;
|
||||
height: 100vh;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 768px) {
|
||||
.leaflet-container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 50vh;
|
||||
}
|
||||
|
||||
.map-position-center {
|
||||
font-size: 10px;
|
||||
top: 50%;
|
||||
right: 5px;
|
||||
margin: 5px;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.map-position-click {
|
||||
font-size: 10px;
|
||||
top: 53%;
|
||||
right: 5px;
|
||||
margin: 5px;
|
||||
padding: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 480px) {
|
||||
.leaflet-container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 50vh;
|
||||
}
|
||||
|
||||
.map-position-center {
|
||||
top: 50%;
|
||||
right: 5px;
|
||||
margin: 5px;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.map-position-click {
|
||||
top: 53%;
|
||||
right: 5px;
|
||||
margin: 5px;
|
||||
padding: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 320px) {
|
||||
.leaflet-container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 50vh;
|
||||
}
|
||||
|
||||
.map-position-center {
|
||||
top: 50%;
|
||||
right: 5px;
|
||||
margin: 5px;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.map-position-click {
|
||||
top: 53%;
|
||||
right: 5px;
|
||||
margin: 5px;
|
||||
padding: 5px;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
.collapsible {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: #F4F4F4;
|
||||
color: #AAAAAA;
|
||||
cursor: pointer;
|
||||
height: 36px;
|
||||
width: 100%;
|
||||
border: none;
|
||||
text-align: left;
|
||||
outline: none;
|
||||
border-radius: 25px;
|
||||
font-weight: 600;
|
||||
font-size: 16px;
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
line-height: 19px;
|
||||
color: #AAAAAA;
|
||||
padding-left: 20px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.active,
|
||||
.collapsible:hover {
|
||||
background-color: #e4e4e4;
|
||||
}
|
||||
|
||||
.content {
|
||||
height: auto;
|
||||
overflow: hidden;
|
||||
background-color: #F4F4F4;
|
||||
padding: 0 18px;
|
||||
margin-bottom: 5px;
|
||||
border-radius: 0px 0px 25px 25px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.collapsible-active {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: #F4F4F4;
|
||||
color: #AAAAAA;
|
||||
cursor: pointer;
|
||||
height: 36px;
|
||||
width: 100%;
|
||||
border: none;
|
||||
text-align: left;
|
||||
outline: none;
|
||||
border-radius: 25px 25px 0px 0px;
|
||||
font-weight: 600;
|
||||
font-size: 16px;
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
line-height: 19px;
|
||||
color: #AAAAAA;
|
||||
flex-grow: 1;
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
.group-icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.participant-text {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
margin-right: 10px;
|
||||
font-size: 12px;
|
||||
color: #050505;
|
||||
}
|
||||
|
||||
.participant-text-selected {
|
||||
display: flex;
|
||||
align-self: flex-end;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
font-size: 12px;
|
||||
color: green;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 2px;
|
||||
}
|
||||
|
||||
.participant-table {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
h4 {
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
td {
|
||||
height: 25px;
|
||||
}
|
||||
|
||||
.collapsible-button-div {
|
||||
width: 100%;
|
||||
height: 50%;
|
||||
border-radius: 25px;
|
||||
z-index: 3;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.collapsible-button {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
border-radius: 25px;
|
||||
background: #007EC1;
|
||||
border: none;
|
||||
outline: none;
|
||||
color: white;
|
||||
font-size: 8px;
|
||||
font-family: 'Cascadia';
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
margin-bottom: 5px;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.collapsible-button:hover {
|
||||
background-color: #005a8c;
|
||||
}
|
||||
|
||||
.choose-popup {
|
||||
position: fixed;
|
||||
width: 10%;
|
||||
height: 8%;
|
||||
top: 10%;
|
||||
left: 50%;
|
||||
background-color: white;
|
||||
color: red;
|
||||
z-index: 3;
|
||||
border-radius: 25px;
|
||||
transform: translate(-50%, -50%);
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.startingpath-popup {
|
||||
position: fixed;
|
||||
width: 10%;
|
||||
height: auto;
|
||||
top: 90%;
|
||||
left: 50%;
|
||||
background-color: white;
|
||||
color: red;
|
||||
z-index: 3;
|
||||
border-radius: 25px;
|
||||
transform: translate(-50%, -50%);
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
+318
@@ -0,0 +1,318 @@
|
||||
.popup {
|
||||
position: fixed;
|
||||
width: 20%;
|
||||
height: 25%;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
background-color: white;
|
||||
z-index: 3;
|
||||
border-radius: 25px;
|
||||
transform: translate(-50%, -50%);
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.popup-blur {
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
z-index: 2;
|
||||
backdrop-filter: blur(3px);
|
||||
}
|
||||
|
||||
.popup-text {
|
||||
position: absolute;
|
||||
top: 5%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, 0);
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.select-party {
|
||||
position: absolute;
|
||||
top: 45%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, 0%);
|
||||
width: 12%;
|
||||
height: 3%;
|
||||
background: #D9D9D9;
|
||||
opacity: 0.5;
|
||||
border-radius: 25px;
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
.select-party-dropdown {
|
||||
width: 35%;
|
||||
height: 100%;
|
||||
background: #D9D9D9;
|
||||
opacity: 0.4;
|
||||
box-shadow: 2px 0px 5px rgba(0, 0, 0, 0.5);
|
||||
border-radius: 25px;
|
||||
z-index: 4;
|
||||
border: none;
|
||||
appearance: none;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.select-party-text {
|
||||
position: absolute;
|
||||
width: 42px;
|
||||
height: 15px;
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
font-size: 12px;
|
||||
line-height: 15px;
|
||||
color: #000000;
|
||||
top: 50%;
|
||||
left: 10px;
|
||||
transform: translateY(-50%);
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
.select-number-inputfield {
|
||||
position: absolute;
|
||||
width: 55%;
|
||||
height: 80%;
|
||||
margin-top: 1%;
|
||||
left: 40%;
|
||||
background-color: #D9D9D9;
|
||||
border: none;
|
||||
border-radius: 25px;
|
||||
}
|
||||
|
||||
.select-number-inputfield:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.select-party-form-select {
|
||||
position: absolute;
|
||||
width: 60%;
|
||||
height: 100%;
|
||||
left: 40%;
|
||||
border: none;
|
||||
background-color: #D9D9D9;
|
||||
border-radius: 25px;
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
.select-number {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, 0);
|
||||
width: 12%;
|
||||
height: 3%;
|
||||
background: #D9D9D9;
|
||||
opacity: 0.5;
|
||||
border-radius: 25px;
|
||||
z-index: 4;
|
||||
}
|
||||
|
||||
.select-number-inputfield-div {
|
||||
width: 35%;
|
||||
height: 100%;
|
||||
background: #D9D9D9;
|
||||
opacity: 0.5;
|
||||
box-shadow: 2px 0px 5px rgba(0, 0, 0, 0.5);
|
||||
border-radius: 25px;
|
||||
border: none;
|
||||
text-align: right;
|
||||
z-index: 4;
|
||||
}
|
||||
|
||||
.select-number-text {
|
||||
position: absolute;
|
||||
width: 53px;
|
||||
height: 15px;
|
||||
left: 10px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
font-size: 12px;
|
||||
line-height: 15px;
|
||||
color: #000000;
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
.popup-buttons {
|
||||
position: absolute;
|
||||
width: 12%;
|
||||
height: 32px;
|
||||
left: 50%;
|
||||
top: 55%;
|
||||
transform: translate(-50%, 0%);
|
||||
background: white;
|
||||
border-radius: 25px;
|
||||
z-index: 3;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.popup-buttons button {
|
||||
width: 45%;
|
||||
height: 25px;
|
||||
border-radius: 25px;
|
||||
background: #007EC1;
|
||||
border: none;
|
||||
outline: none;
|
||||
color: white;
|
||||
font-size: 12px;
|
||||
font-family: 'Cascadia';
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.popup-cancel {
|
||||
border-radius: 25px;
|
||||
}
|
||||
|
||||
.popup-submit {
|
||||
border-radius: 25px;
|
||||
}
|
||||
|
||||
.popup-buttons button:hover,
|
||||
.popup-buttons button:focus {
|
||||
background-color: #005a8c;
|
||||
}
|
||||
|
||||
textarea:focus,
|
||||
input:focus {
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
/* Chrome, Safari, Edge, Opera */
|
||||
input::-webkit-outer-spin-button,
|
||||
input::-webkit-inner-spin-button {
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* Firefox */
|
||||
input[type=number] {
|
||||
-moz-appearance: textfield;
|
||||
appearance: textfield;
|
||||
}
|
||||
|
||||
/* Media queries for responsive design */
|
||||
@media only screen and (max-width: 1024px) {
|
||||
.popup {
|
||||
width: 40%;
|
||||
height: 25%;
|
||||
}
|
||||
|
||||
.select-party,
|
||||
.select-number {
|
||||
width: 20%;
|
||||
height: 4%;
|
||||
}
|
||||
|
||||
.popup-buttons {
|
||||
width: 20%;
|
||||
height: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 768px) {
|
||||
.popup {
|
||||
width: 40%;
|
||||
height: 25%;
|
||||
}
|
||||
|
||||
.select-party,
|
||||
.select-number {
|
||||
width: 25%;
|
||||
height: 5%;
|
||||
}
|
||||
|
||||
.popup-buttons {
|
||||
width: 25%;
|
||||
height: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 480px) {
|
||||
.popup {
|
||||
width: 50%;
|
||||
height: 25%;
|
||||
}
|
||||
|
||||
.select-party,
|
||||
.select-number {
|
||||
margin-top: 5px;
|
||||
width: 35%;
|
||||
height: 3%;
|
||||
}
|
||||
|
||||
.popup-buttons {
|
||||
width: 35%;
|
||||
height: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 320px) {
|
||||
.popup {
|
||||
width: 50%;
|
||||
height: 25%;
|
||||
}
|
||||
|
||||
.select-party,
|
||||
.select-number {
|
||||
width: 40%;
|
||||
height: 3%;
|
||||
}
|
||||
|
||||
.popup-buttons {
|
||||
width: 40%;
|
||||
height: 25px;
|
||||
}
|
||||
|
||||
.popup-text {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.select-party-text,
|
||||
.select-number-text {
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.popup-buttons button {
|
||||
font-size: 10px;
|
||||
width: 30%;
|
||||
height: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-height: 600px) {
|
||||
.popup {
|
||||
width: 50%;
|
||||
height: 25%;
|
||||
}
|
||||
|
||||
.select-party,
|
||||
.select-number {
|
||||
width: 40%;
|
||||
height: 3%;
|
||||
}
|
||||
|
||||
.popup-buttons {
|
||||
width: 40%;
|
||||
height: 25px;
|
||||
}
|
||||
|
||||
.popup-text {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.select-party-text,
|
||||
.select-number-text {
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.popup-buttons button {
|
||||
font-size: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
+211
@@ -0,0 +1,211 @@
|
||||
.sidebar {
|
||||
position: absolute;
|
||||
width: 20%;
|
||||
height: 100vh;
|
||||
background: #FFFFFF;
|
||||
box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.25);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.participant-list {
|
||||
width: 80%;
|
||||
height: 36px;
|
||||
background: #F4F4F4;
|
||||
border-radius: 25px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.participant-list-text {
|
||||
font-weight: 600;
|
||||
font-size: 90%;
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
line-height: 19px;
|
||||
color: #777777;
|
||||
flex-grow: 1;
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
.addButton-div {
|
||||
width: 15%;
|
||||
height: 36px;
|
||||
background: #007EC1;
|
||||
box-shadow: -2px 1px 7.6px rgba(0, 0, 0, 0.25);
|
||||
border-radius: 25px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.addButton {
|
||||
color: white;
|
||||
font-size: 16px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.sidebar-content {
|
||||
width: 80%;
|
||||
height: 87%;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin-top: 5px;
|
||||
margin-bottom: 20px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.logo {
|
||||
margin-top: 20px;
|
||||
margin-left: 0.5cm;
|
||||
margin-right: 0.5cm;
|
||||
width: 150px;
|
||||
height: auto;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1100px) {
|
||||
.sidebar {
|
||||
width: 40%;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.participant-list,
|
||||
.sidebar-content {
|
||||
width: 90%;
|
||||
}
|
||||
|
||||
.logo {
|
||||
width: 120px;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1024px) {
|
||||
.sidebar {
|
||||
width: 40%;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.participant-list,
|
||||
.sidebar-content {
|
||||
width: 90%;
|
||||
}
|
||||
|
||||
.logo {
|
||||
width: 120px;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 768px) {
|
||||
.sidebar {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 50vh;
|
||||
}
|
||||
|
||||
.participant-list,
|
||||
.sidebar-content {
|
||||
width: 90%;
|
||||
}
|
||||
|
||||
.logo {
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
.addButton-div {
|
||||
width: 10%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 480px) {
|
||||
.sidebar {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 50vh;
|
||||
}
|
||||
|
||||
.participant-list,
|
||||
.sidebar-content {
|
||||
width: 90%;
|
||||
}
|
||||
|
||||
.logo {
|
||||
width: 80px;
|
||||
}
|
||||
|
||||
.addButton-div {
|
||||
width: 15%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 320px) {
|
||||
.sidebar {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 50vh;
|
||||
}
|
||||
|
||||
.participant-list,
|
||||
.sidebar-content {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.logo {
|
||||
width: 60px;
|
||||
}
|
||||
|
||||
.addButton-div {
|
||||
width: 20%;
|
||||
}
|
||||
|
||||
.addButton {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar-footer {
|
||||
height: 3%;
|
||||
align-content: space-between;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.sidebar-footer button {
|
||||
margin-left: 5px;
|
||||
margin-right: 5px;
|
||||
height: 100%;
|
||||
border-radius: 25px;
|
||||
background: #007EC1;
|
||||
border: none;
|
||||
outline: none;
|
||||
color: white;
|
||||
font-size: 12px;
|
||||
font-family: 'Cascadia';
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.sidebar-footer button:hover,
|
||||
.sidebar-footer button:focus {
|
||||
background-color: #005a8c;
|
||||
}
|
||||
|
||||
|
||||
.sidebar-startStop-container {
|
||||
position: fixed;
|
||||
width: 30%;
|
||||
height: 7.5%;
|
||||
background-color: #FFFFFF;
|
||||
z-index: 5;
|
||||
top: 5%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
border-radius: 25px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.sidebar-startStop-container button {
|
||||
width: 30%;
|
||||
height: 50%;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import { participantError } from "../store/participantSlice";
|
||||
/**
|
||||
* Verwendet einen WebSocket, um die ABC-Schutzinformationen eines Teilnehmers zu ändern.
|
||||
*
|
||||
* Diese Funktion stellt eine Verbindung zu einem WebSocket-Server her und sendet eine Anfrage,
|
||||
* um die ABC-Schutzinformationen eines bestimmten Teilnehmers zu ändern. Nach dem Senden der Anfrage
|
||||
* wird die Verbindung sofort geschlossen. Im Fehlerfall wird ein Fehlerzustand über Redux dispatcht.
|
||||
*
|
||||
* @param {string} equipmentNumber - Die Ausrüstungsnummer des Teilnehmers, dessen ABC-Schutzinformationen geändert werden sollen.
|
||||
* @param {boolean} abcProtection - Der neue Zustand des ABC-Schutzes für den Teilnehmer (true für aktiviert, false für deaktiviert).
|
||||
* @param {string} exerciseId - Die ID der Übung, zu der der Teilnehmer gehört.
|
||||
* @param {function} dispatch - Die Redux Dispatch-Funktion, um den Fehlerzustand zu aktualisieren, falls ein Fehler auftritt.
|
||||
*/
|
||||
export function useChangeAbcProtection(equipmentNumber, abcProtection, exerciseId, dispatch) {
|
||||
const socket = new WebSocket('ws://localhost:6969/participantHandler');
|
||||
socket.onopen = function open() {
|
||||
var req = JSON.stringify({
|
||||
type: 'changeAbcProtection',
|
||||
excerciseId: parseInt(exerciseId),
|
||||
participantData: { "participantId": parseInt(equipmentNumber), "abcProtection": abcProtection }
|
||||
})
|
||||
socket.send(req);
|
||||
socket.close()
|
||||
};
|
||||
socket.onerror = () => {
|
||||
dispatch(participantError({ isError: true, errorMessage: "ERROR: There was an error with the participant websocket!" }))
|
||||
};
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
import { addParticipant, participantError } from "../store/participantSlice";
|
||||
/**
|
||||
* Überprüft, ob ein Teilnehmer für eine bestimmte Übung gültig ist, indem eine WebSocket-Verbindung verwendet wird.
|
||||
*
|
||||
* Diese Funktion initiiert eine WebSocket-Verbindung zu einem Server und sendet eine Anfrage, um zu überprüfen,
|
||||
* ob ein Teilnehmer für eine bestimmte Übung gültig ist. Die Anfrage enthält die Übungs-ID und die Teilnehmer-ID.
|
||||
* Bei einem Fehler in der WebSocket-Verbindung wird ein Fehlerzustand über die `participantError`-Aktion im Redux-Store
|
||||
* dispatcht. Nach dem Öffnen der Verbindung wird die Anfrage gesendet. Bei Erhalt einer Nachricht vom Server (die Antwort
|
||||
* auf die Gültigkeitsprüfung) wird die Antwort verarbeitet. Die Informationen über die
|
||||
* Zugehörigkeit des Teilnehmers beinhaltet die PartyId, die dann weiter verwendet werden können.
|
||||
*
|
||||
* @param {string} equipmentNumber - Die ID des Teilnehmers, dessen Gültigkeit überprüft werden soll.
|
||||
* @param {string} exerciseId - Die ID der Übung, für die die Gültigkeit des Teilnehmers überprüft werden soll.
|
||||
* @param {function} dispatch - Die Redux Dispatch-Funktion, die verwendet wird, um Aktionen im Redux-Store zu dispatchen.
|
||||
* @param {Array<Object>} partyList - Eine Liste von Parteien, die in der Überprüfung verwendet wird, um Details zum Teilnehmer zu ermitteln.
|
||||
* @param {string} _partyId - Eine vorläufige Partei-ID, die in der Funktion verwendet wird (der genaue Verwendungszweck ist aus dem Auszug nicht ersichtlich).
|
||||
*/
|
||||
export function useCheckIfParticipantIsValid(equipmentNumber, exerciseId, dispatch, partyList, _partyId) {
|
||||
const socket = new WebSocket('ws://localhost:6969/participantHandler');
|
||||
socket.onerror = (error) => {
|
||||
dispatch(participantError({ isError: true, errorMessage: "ERROR: There was an error with the participant websocket!" }))
|
||||
};
|
||||
|
||||
socket.onopen = function open() {
|
||||
var req = JSON.stringify({
|
||||
type: 'checkIfParticipantIsValid',
|
||||
excerciseId: parseInt(exerciseId),
|
||||
participantData: { "participantId": parseInt(equipmentNumber) }
|
||||
})
|
||||
socket.send(req);
|
||||
};
|
||||
socket.onmessage = function incoming(event) {
|
||||
const participant = JSON.parse(event.data);
|
||||
var partyName = ""
|
||||
var partyId = ""
|
||||
for(var party of partyList) {
|
||||
if(party.id === participant.partyId) {
|
||||
partyName = party.party;
|
||||
partyId = party.id
|
||||
}
|
||||
}
|
||||
if (participant.valid && partyId === parseInt(_partyId)) {
|
||||
dispatch(addParticipant({ equipmentNumber: participant.equipmentNumber, currentPosition: [53.0580229, 8.8972232], abcProtection: false, type: participant.tacticalIdentifier, party: partyName, exerciseId: exerciseId, isWalking: false, walkingMode: "", path: []}));
|
||||
} else {
|
||||
dispatch(participantError({isError: true, errorMessage: "Participant is not valid! Group or Equipment Number could be wrong!"}))
|
||||
}
|
||||
socket.close();
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
|
||||
import { addParticipant, participantError } from "../store/participantSlice";
|
||||
|
||||
let req = ""
|
||||
export function getAllParticipants(exerciseId, dispatch, partyList) {
|
||||
var socket = new WebSocket('ws://localhost:6969/participantHandler');
|
||||
|
||||
socket.onerror = (error) => {
|
||||
dispatch(participantError({ isError: true, errorMessage: "ERROR: There was an error with the participant websocket!" }))
|
||||
};
|
||||
|
||||
socket.onopen = function open() {
|
||||
req = JSON.stringify(
|
||||
{type: "getAllParticipants", excerciseId: 42}
|
||||
)
|
||||
socket.send(req);
|
||||
};
|
||||
socket.onmessage = function incoming(event) {
|
||||
const participants = JSON.parse(event.data);
|
||||
|
||||
var blueCount = 0;
|
||||
var redCount = 0;
|
||||
|
||||
socket.onmessage = () => {}
|
||||
|
||||
for(var participant of participants) {
|
||||
var partyName = ""
|
||||
for (var party of partyList) {
|
||||
if (party.id === participant.partyId) {
|
||||
partyName = party.party;
|
||||
}
|
||||
}
|
||||
if (participant.valid) {
|
||||
if(participant.partyId === 2 ) {
|
||||
dispatch(addParticipant({ equipmentNumber: participant.equipmentNumber, currentPosition: [53.044 + 0.0005 * blueCount , 8.814], abcProtection: false, type: participant.tacticalIdentifier, party: partyName, exerciseId: exerciseId, isWalking: false, walkingMode: "", path: [] }));
|
||||
req = JSON.stringify({
|
||||
type: 'changePosition',
|
||||
excerciseId: parseInt(exerciseId),
|
||||
participantData: { "participantId": parseInt(participant.equipmentNumber), "positions": [{"lat": 53.044 + 0.0005 * blueCount, "lng": 8.814}] }
|
||||
})
|
||||
socket.send(req);
|
||||
blueCount++;
|
||||
} else if(participant.partyId === 3) {
|
||||
dispatch(addParticipant({ equipmentNumber: participant.equipmentNumber, currentPosition: [53.044 + 0.0005 * redCount , 8.959], abcProtection: false, type: participant.tacticalIdentifier, party: partyName, exerciseId: exerciseId, isWalking: false, walkingMode: "", path: [] }));
|
||||
req = JSON.stringify({
|
||||
type: 'changePosition',
|
||||
excerciseId: parseInt(exerciseId),
|
||||
participantData: { "participantId": parseInt(participant.equipmentNumber), "positions": [{"lat": 53.044 + 0.0005 * redCount, "lng": 8.959}] }
|
||||
})
|
||||
socket.send(req);
|
||||
redCount++
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import { setExcerciseId, exerciseError } from "../store/exerciseSlice";
|
||||
|
||||
/**
|
||||
* Verwendet einen WebSocket, um eine Liste von Übungs-IDs vom Server zu erhalten.
|
||||
*
|
||||
* Diese Funktion stellt eine Verbindung zu einem WebSocket-Server her und wartet auf eine Nachricht,
|
||||
* die eine Liste von Übungs-IDs enthält. Sobald die Nachricht empfangen wird, wird die Liste der IDs
|
||||
* in einem Redux-Store gespeichert, indem die `setExcerciseId`-Aktion dispatched wird. Nach dem Empfang
|
||||
* der Daten wird die WebSocket-Verbindung geschlossen. Im Falle eines Fehlers beim Empfangen der Daten
|
||||
* wird ein Fehlerzustand über die `exerciseError`-Aktion im Redux-Store gesetzt.
|
||||
*
|
||||
* @param {function} dispatch - Die Redux Dispatch-Funktion, die verwendet wird, um Aktionen im Redux-Store zu dispatchen.
|
||||
*/
|
||||
export function useGetExerciseIds(dispatch) {
|
||||
|
||||
const socket = new WebSocket('ws://localhost:6969/exerciseIds');
|
||||
const exerciseIds = [];
|
||||
|
||||
socket.onmessage = function incoming(event) {
|
||||
for (var exerciseId of JSON.parse(event.data)) {
|
||||
exerciseIds.push(exerciseId);
|
||||
}
|
||||
dispatch(setExcerciseId(exerciseIds))
|
||||
socket.close();
|
||||
};
|
||||
socket.onerror = () => {
|
||||
dispatch(exerciseError({ isError: true, errorMessage: "ERROR: There was an error with the exerciseIds websocket!" }))
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import { partyDataError, setPartyData } from '../store/partySlice.js';
|
||||
/**
|
||||
* Stellt eine WebSocket-Verbindung her, um Parteidaten basierend auf einer Übungs-ID zu erhalten.
|
||||
*
|
||||
* Diese Funktion initiiert eine WebSocket-Verbindung zu einem vorgegebenen Server und sendet eine Anfrage
|
||||
* mit der Übungs-ID, um spezifische Partydaten zu erhalten. Sobald die Verbindung geöffnet ist, wird die
|
||||
* Übungs-ID an den Server gesendet. Bei Empfang der Daten (über die `onmessage`-Ereignisbehandlung) werden
|
||||
* die empfangenen Parteidaten durch Dispatchen der `setPartyData`-Aktion im Redux-Store gespeichert.
|
||||
* Im Falle eines Verbindungsfehlers wird ein Fehlerzustand durch Dispatchen der `partyDataError`-Aktion
|
||||
* im Redux-Store gesetzt. Nach erfolgreichem Empfang der Daten wird die WebSocket-Verbindung geschlossen.
|
||||
*
|
||||
* @param {string} exerciseId - Die ID der Übung, für die Parteidaten abgerufen werden sollen.
|
||||
* @param {function} dispatch - Die Redux Dispatch-Funktion, die verwendet wird, um Aktionen im Redux-Store zu dispatchen.
|
||||
*/
|
||||
export function useGetPartyData(exerciseId, dispatch) {
|
||||
const socket = new WebSocket('ws://localhost:6969/partyData');
|
||||
socket.onopen = function open() {
|
||||
socket.send(JSON.stringify({
|
||||
excerciseId: parseInt(exerciseId),
|
||||
}));
|
||||
};
|
||||
socket.onmessage = function incoming(event) {
|
||||
for (var party of JSON.parse(event.data)) {
|
||||
dispatch(setPartyData(party))
|
||||
}
|
||||
socket.close();
|
||||
};
|
||||
socket.onerror = (error) => {
|
||||
dispatch(partyDataError({ isError: true, errorMessage: "ERROR: There was an error with the partyData websocket!" }))
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Verwendet einen WebSocket, um eine Positionsänderung eines Teilnehmers zu senden.
|
||||
*
|
||||
* Diese Funktion stellt eine Verbindung zu einem WebSocket-Server her und sendet eine Anfrage,
|
||||
* um die Position eines Teilnehmers zu aktualisieren. Die Anfrage enthält die Übungs-ID, die Ausrüstungsnummer
|
||||
* des Teilnehmers und die neue geographische Position. Nach dem Senden der Anfrage wird die Verbindung
|
||||
* sofort geschlossen. Im Fehlerfall wird ein Fehlerzustand über Redux dispatcht, um die Anwendung
|
||||
* über das Problem zu informieren.
|
||||
*
|
||||
* @param {string} equipmentNumber - Die Ausrüstungsnummer des Teilnehmers, dessen Position geändert werden soll.
|
||||
* @param {Array<number>} geoPosition - Ein Array mit zwei Elementen, wobei das erste Element die Breitengrad- und das zweite Element die Längengradkoordinate ist.
|
||||
* @param {string} exerciseId - Die ID der Übung, zu der der Teilnehmer gehört.
|
||||
* @param {function} dispatch - Die Redux Dispatch-Funktion, um den Fehlerzustand zu aktualisieren, falls ein Fehler auftritt.
|
||||
*/
|
||||
export function useSendPositionChange(equipmentNumber, geoPosition, exerciseId) {
|
||||
const socket = new WebSocket('ws://localhost:6969/participantHandler');
|
||||
socket.onopen = function open() {
|
||||
var req = JSON.stringify({
|
||||
type: 'changePosition',
|
||||
excerciseId: parseInt(exerciseId),
|
||||
participantData: { "participantId": parseInt(equipmentNumber), "positions": [{"lat" : geoPosition[0], "lng": geoPosition[1]}] }
|
||||
})
|
||||
socket.send(req);
|
||||
socket.close()
|
||||
};
|
||||
}
|
||||
Reference in new issue
Block a user