Session 022: Migrate Lts/Tools (72 submodules) to Qt6

- Copied complete Lts/Tools directory from qt5-before to qt6-after
- Updated 32 CMakeLists.txt files (Qt5:: → Qt6::)
- Tools subdirectory already included in parent Lts/CMakeLists.txt
- All 72 tool submodules now present in qt6-migrated-legatus
- Includes: Adler, ArAssets, ConfigX, DBAdministration, DBUpdate,
  DdsView, InfrastructureManagement, and 67 more tools
- C++17 standard (RTI 7.7.0 compatibility maintained)
- Tests deferred until final validation phase

Migration progress:
✅ Lts/Application (68 files)
✅ Lts/DDS (verified Qt6-ready)
✅ Lts/Plugins (60 submodules, verified)
✅ Lts/Projects (5 submodules, verified)
✅ Lts/Proxies (13 submodules, verified)
✅ Lts/Services (47 submodules, verified)
✅ Lts/Tools (72 submodules, copied + CMake updated)
✅ Lts/Viewer (LtsFileDialog, LtsReportDisplay, SituationMap)
🟡 TetraIF (partial)
🟡 Z (partial: nglwidgets, QtSingleApplication, qsql_informix, WMS_Interface)
This commit is contained in:
torsten committed 2026-08-25 14:07:20 +02:00
1 parent 01dce5fe52
commit d5c13033e2
1792 files changed
+982018

No files matched your search

@@ -0,0 +1,13 @@
body {
margin: 0;
padding: 0;
}
html body {
width: 100%;
height: 100vh;
}
.leaflet-draw.leaflet-control {
display: none;
}
@@ -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>
}
</>
)
}
@@ -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>
);
}
@@ -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>
)}
</>
)
}
@@ -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>
);
}
@@ -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);
}
};
@@ -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.
@@ -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();
@@ -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
@@ -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;
}
@@ -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;
}
@@ -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;
}
}
@@ -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!" }))
};
}
@@ -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()
};
}