Initial commit
This commit is contained in:
commit
b7d90b9afe
16245 files changed
+3396666
No files matched your search
BIN
Binary file not shown.
BIN
Binary file not shown.
Executable
+17
@@ -0,0 +1,17 @@
|
||||
module legatus-rme/backend
|
||||
|
||||
go 1.21.5
|
||||
|
||||
require github.com/gorilla/websocket v1.5.1
|
||||
|
||||
require (
|
||||
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.20.0 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
golang.org/x/crypto v0.23.0 // indirect
|
||||
golang.org/x/net v0.25.0 // indirect
|
||||
golang.org/x/sys v0.20.0 // indirect
|
||||
golang.org/x/text v0.15.0 // indirect
|
||||
)
|
||||
Executable
+22
@@ -0,0 +1,22 @@
|
||||
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
|
||||
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
|
||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||
github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8=
|
||||
github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
|
||||
github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
|
||||
github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
|
||||
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
|
||||
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
||||
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
Executable
+21
@@ -0,0 +1,21 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
serviceportsinformation "legatus-rme/backend/servicePortsInformation"
|
||||
typedata "legatus-rme/backend/typeData"
|
||||
"legatus-rme/backend/websocket"
|
||||
)
|
||||
|
||||
func main() {
|
||||
servicePortsInformationSocket := websocket.OpenWebsocket("servicePortsInformation", []int{7300}).(serviceportsinformation.ServicePortsInformation)
|
||||
exerciseServicePortMap, exerciseIds := servicePortsInformationSocket.GetPorts()
|
||||
|
||||
typeDataSocket := websocket.OpenWebsocket("typeBaseData", []int{exerciseServicePortMap["0-ParticipantManagement1"]}).(typedata.TypeData)
|
||||
typeData := typeDataSocket.GetTypeData()
|
||||
|
||||
websocket.OpenWebsocketServer(websocket.Server{
|
||||
ExerciseIds: exerciseIds,
|
||||
ExerciseServicePortMap: exerciseServicePortMap,
|
||||
TypeData: typeData,
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package participanthandler
|
||||
|
||||
import "time"
|
||||
|
||||
// ChangeAbcProtection ändert den ABC-Schutzstatus eines Geräts.
|
||||
// equipmentNumber: Die Nummer des Geräts, dessen ABC-Schutzstatus geändert werden soll.
|
||||
// abcProtection: Der neue Status des ABC-Schutzes (true für aktiv, false für inaktiv).
|
||||
// Rückgabewert: Gibt einen Fehler zurück, falls die Änderung fehlschlägt.
|
||||
func (c *ParticipantHandler) ChangeAbcProtection(equipmentNumber int, abcProtection bool) error {
|
||||
// Erstellen einer Anfrage zur Änderung des ABC-Schutzstatus
|
||||
req := AbcProtectionChangeRequest{
|
||||
Data: struct {
|
||||
Header struct {
|
||||
EquipmentNumber int `json:"equipmentNumber"`
|
||||
TimeStamp int `json:"timeStamp"`
|
||||
} `json:"header"`
|
||||
NbcProtectionActive bool `json:"nbcProtectionActive"`
|
||||
}{
|
||||
Header: struct {
|
||||
EquipmentNumber int `json:"equipmentNumber"`
|
||||
TimeStamp int `json:"timeStamp"`
|
||||
}{
|
||||
EquipmentNumber: equipmentNumber,
|
||||
TimeStamp: int(time.Now().UnixMilli()),
|
||||
},
|
||||
NbcProtectionActive: abcProtection,
|
||||
},
|
||||
TypeID: 7,
|
||||
}
|
||||
|
||||
// Senden der Anfrage über den MariusSocket
|
||||
if err := c.MariusSocket.WriteJSON(req); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package participanthandler
|
||||
|
||||
import (
|
||||
"log"
|
||||
"math"
|
||||
"time"
|
||||
)
|
||||
|
||||
// degreesToRadians konvertiert Grad in Radiant.
|
||||
// degrees: Der Winkel in Grad.
|
||||
// Rückgabewert: Der Winkel in Radiant.
|
||||
func degreesToRadians(degrees float64) float64 {
|
||||
return degrees * (math.Pi / 180)
|
||||
}
|
||||
|
||||
// ChangePosition ändert die Position eines Geräts.
|
||||
// equipmentNumber: Die Nummer des Geräts, dessen Position geändert werden soll.
|
||||
// geoPosition: Die neue geografische Position des Geräts.
|
||||
// Rückgabewert: Gibt einen Fehler zurück, falls die Änderung fehlschlägt.
|
||||
func (c *ParticipantHandler) ChangePosition(equipmentNumber int, geoPosition *GeoPosition) error {
|
||||
log.Println(equipmentNumber, geoPosition)
|
||||
|
||||
// Erstellen einer Anfrage zur Änderung der Position
|
||||
req := PositionChangeRequest{
|
||||
Data: struct {
|
||||
Header struct {
|
||||
EquipmentNumber int `json:"equipmentNumber"`
|
||||
TimeStamp int `json:"timeStamp"`
|
||||
} `json:"header"`
|
||||
GeoPosition struct {
|
||||
Lon float64 `json:"lon"`
|
||||
Lat float64 `json:"lat"`
|
||||
Z float64 `json:"z"`
|
||||
} `json:"geoPosition"`
|
||||
}{
|
||||
Header: struct {
|
||||
EquipmentNumber int `json:"equipmentNumber"`
|
||||
TimeStamp int `json:"timeStamp"`
|
||||
}{
|
||||
EquipmentNumber: equipmentNumber,
|
||||
TimeStamp: int(time.Now().UnixMilli()),
|
||||
},
|
||||
GeoPosition: struct {
|
||||
Lon float64 `json:"lon"`
|
||||
Lat float64 `json:"lat"`
|
||||
Z float64 `json:"z"`
|
||||
}{
|
||||
Lon: degreesToRadians(geoPosition.Lng),
|
||||
Lat: degreesToRadians(geoPosition.Lat),
|
||||
Z: 0.0,
|
||||
},
|
||||
},
|
||||
TypeID: 21,
|
||||
}
|
||||
|
||||
// Senden der Anfrage über den MariusSocket
|
||||
if err := c.MariusSocket.WriteJSON(req); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
package participanthandler
|
||||
|
||||
import "log"
|
||||
|
||||
// CheckIfParticipantIsValid überprüft, ob ein Teilnehmer gültig ist.
|
||||
// equipmentNumber: Die Nummer des Geräts, dessen Gültigkeit überprüft werden soll.
|
||||
// Rückgabewert: Gibt eine Struktur zurück, die die Gültigkeit des Teilnehmers und seine Daten enthält.
|
||||
func (c *ParticipantHandler) CheckIfParticipantIsValid(equipmentNumber int) CheckIfParticipantIsValidReturn {
|
||||
// Erstellen einer Anfrage zur Überprüfung der Teilnehmergültigkeit
|
||||
req := CheckIfParticipantIsValidRequest{Type: "baseDlRequest", UUID: "UUID"}
|
||||
|
||||
// Senden der Anfrage über das ParticipantManagement
|
||||
c.ParticipantManagement.WriteJSON(req)
|
||||
|
||||
var res CheckIfParticipantIsValidResponse
|
||||
for {
|
||||
// Lesen der Antwort vom ParticipantManagement
|
||||
err := c.ParticipantManagement.ReadJSON(&res)
|
||||
if err != nil {
|
||||
log.Fatal("CheckIfParticipantIsValidRequest - Error getting participant data:", err)
|
||||
}
|
||||
|
||||
// Überprüfen, ob die Antwort den erwarteten Typ hat
|
||||
if res.Type == "participantBaseData" {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
var participantData *CheckIfParticipantIsValidReturn
|
||||
// Durchlaufen der Teilnehmerdaten, um den passenden Teilnehmer zu finden
|
||||
for _, participant := range res.Data {
|
||||
if participant.EquipmentNumber == equipmentNumber {
|
||||
participantData = &CheckIfParticipantIsValidReturn{
|
||||
TacticalIdentifier: participant.TacticalIdentifier,
|
||||
Designator: participant.Designator,
|
||||
EquipmentNumber: participant.EquipmentNumber,
|
||||
Valid: true,
|
||||
PartyId: participant.PartyID,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Falls kein passender Teilnehmer gefunden wurde, wird eine ungültige Struktur zurückgegeben
|
||||
if participantData == nil {
|
||||
participantData = &CheckIfParticipantIsValidReturn{
|
||||
TacticalIdentifier: "",
|
||||
Designator: "",
|
||||
EquipmentNumber: 0,
|
||||
Valid: false,
|
||||
PartyId: 0,
|
||||
}
|
||||
}
|
||||
|
||||
return *participantData
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package participanthandler
|
||||
|
||||
import "log"
|
||||
|
||||
func (c *ParticipantHandler) GetAllParticipants() []*CheckIfParticipantIsValidReturn {
|
||||
// Erstellen einer Anfrage zur Überprüfung der Teilnehmergültigkeit
|
||||
req := CheckIfParticipantIsValidRequest{Type: "baseDlRequest", UUID: "UUID"}
|
||||
|
||||
// Senden der Anfrage über das ParticipantManagement
|
||||
c.ParticipantManagement.WriteJSON(req)
|
||||
|
||||
var res CheckIfParticipantIsValidResponse
|
||||
for {
|
||||
// Lesen der Antwort vom ParticipantManagement
|
||||
err := c.ParticipantManagement.ReadJSON(&res)
|
||||
if err != nil {
|
||||
log.Fatal("CheckIfParticipantIsValidRequest - Error getting participant data:", err)
|
||||
}
|
||||
|
||||
// Überprüfen, ob die Antwort den erwarteten Typ hat
|
||||
if res.Type == "participantBaseData" {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
var participantData []*CheckIfParticipantIsValidReturn
|
||||
// Durchlaufen der Teilnehmerdaten, um den passenden Teilnehmer zu finden
|
||||
for _, participant := range res.Data {
|
||||
if participant.PartyID == 2 || participant.PartyID == 3 || participant.PartyID == 4 {
|
||||
if participant.EquipmentNumber != 0 {
|
||||
participantData = append(participantData, &CheckIfParticipantIsValidReturn{
|
||||
TacticalIdentifier: participant.TacticalIdentifier,
|
||||
Designator: participant.Designator,
|
||||
EquipmentNumber: participant.EquipmentNumber,
|
||||
Valid: true,
|
||||
PartyId: participant.PartyID,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return participantData
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
package participanthandler
|
||||
|
||||
import (
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
// ParticipantHandler verwaltet die Verbindungen und Anfragen für Teilnehmer.
|
||||
type ParticipantHandler struct {
|
||||
ParticipantManagement *websocket.Conn // Verbindung zum Participant Management
|
||||
MariusSocket *websocket.Conn // Verbindung zum Marius Socket
|
||||
}
|
||||
|
||||
// CheckIfParticipantIsValidRequest repräsentiert eine Anfrage zur Überprüfung der Gültigkeit eines Teilnehmers.
|
||||
type CheckIfParticipantIsValidRequest struct {
|
||||
Type string `json:"type"` // Typ der Anfrage
|
||||
UUID string `json:"uuid"` // Eindeutige Identifikationsnummer der Anfrage
|
||||
}
|
||||
|
||||
// CheckIfParticipantIsValidResponse repräsentiert die Antwort auf eine Anfrage zur Überprüfung der Gültigkeit eines Teilnehmers.
|
||||
type CheckIfParticipantIsValidResponse struct {
|
||||
Data []struct {
|
||||
AssignState int `json:"assignState"` // Zuweisungsstatus
|
||||
Designator string `json:"designator"` // Designator des Teilnehmers
|
||||
EquipmentNumber int `json:"equipmentNumber"` // Gerätenummer
|
||||
ExternalID int `json:"externalId"` // Externe ID
|
||||
HierarchyNumber int `json:"hierarchyNumber"` // Hierarchienummer
|
||||
ID int `json:"id"` // ID des Teilnehmers
|
||||
Identification string `json:"identification"` // Identifikation
|
||||
Info string `json:"info"` // Zusätzliche Informationen
|
||||
IsDeleted bool `json:"isDeleted"` // Löschstatus
|
||||
IsSimulated bool `json:"isSimulated"` // Simulationsstatus
|
||||
Name string `json:"name"` // Name des Teilnehmers
|
||||
PartyID int `json:"partyId"` // Party ID
|
||||
Properties string `json:"properties"` // Eigenschaften
|
||||
SuperiorID int `json:"superiorId"` // ID des Vorgesetzten
|
||||
Symbol string `json:"symbol"` // Symbol
|
||||
TacticalIdentifier string `json:"tacticalIdentifier"` // Taktischer Identifikator
|
||||
TypeID int `json:"typeId"` // Typ ID
|
||||
} `json:"data"` // Liste der Teilnehmerdaten
|
||||
Type string `json:"type"` // Typ der Antwort
|
||||
UUID string `json:"uuid"` // Eindeutige Identifikationsnummer der Antwort
|
||||
}
|
||||
|
||||
// CheckIfParticipantIsValidReturn repräsentiert die Rückgabewerte der Überprüfung der Gültigkeit eines Teilnehmers.
|
||||
type CheckIfParticipantIsValidReturn struct {
|
||||
TacticalIdentifier string `json:"tacticalIdentifier"` // Taktischer Identifikator
|
||||
Designator string `json:"designator"` // Designator des Teilnehmers
|
||||
EquipmentNumber int `json:"equipmentNumber"` // Gerätenummer
|
||||
Valid bool `json:"valid"` // Gültigkeitsstatus
|
||||
PartyId int `json:"partyId"` // Party ID
|
||||
}
|
||||
|
||||
// AbcProtectionChangeRequest repräsentiert eine Anfrage zur Änderung des ABC-Schutzstatus.
|
||||
type AbcProtectionChangeRequest struct {
|
||||
Data struct {
|
||||
Header struct {
|
||||
EquipmentNumber int `json:"equipmentNumber"` // Gerätenummer
|
||||
TimeStamp int `json:"timeStamp"` // Zeitstempel
|
||||
} `json:"header"`
|
||||
NbcProtectionActive bool `json:"nbcProtectionActive"` // Status des ABC-Schutzes
|
||||
} `json:"data"`
|
||||
TypeID int `json:"typeId"` // Typ ID der Anfrage
|
||||
}
|
||||
|
||||
// GeoPosition repräsentiert eine geografische Position.
|
||||
type GeoPosition struct {
|
||||
Lat float64 // Breitengrad
|
||||
Lng float64 // Längengrad
|
||||
}
|
||||
|
||||
// PositionChangeRequest repräsentiert eine Anfrage zur Änderung der Position eines Geräts.
|
||||
type PositionChangeRequest struct {
|
||||
Data struct {
|
||||
Header struct {
|
||||
EquipmentNumber int `json:"equipmentNumber"` // Gerätenummer
|
||||
TimeStamp int `json:"timeStamp"` // Zeitstempel
|
||||
} `json:"header"`
|
||||
GeoPosition struct {
|
||||
Lon float64 `json:"lon"` // Längengrad in Radiant
|
||||
Lat float64 `json:"lat"` // Breitengrad in Radiant
|
||||
Z float64 `json:"z"` // Höhe (standardmäßig 0.0)
|
||||
} `json:"geoPosition"`
|
||||
} `json:"data"`
|
||||
TypeID int `json:"typeId"` // Typ ID der Anfrage
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package partydata
|
||||
|
||||
import (
|
||||
"log"
|
||||
)
|
||||
|
||||
func (c *PartyData) GetPartyData() PartyDataResponse {
|
||||
req := PartyDataRequest{Type: "baseDlRequest", UUID: "UUID"}
|
||||
|
||||
c.Websocket.WriteJSON(req)
|
||||
|
||||
var res PartyDataResponse
|
||||
for {
|
||||
err := c.Websocket.ReadJSON(&res)
|
||||
if err != nil {
|
||||
log.Fatal("PartyDataRequest - Error getting party data:", err)
|
||||
}
|
||||
|
||||
if res.Type == "partyBaseData" {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
c.Websocket.Close()
|
||||
|
||||
return res
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
package partydata
|
||||
|
||||
import (
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
type PartyData struct {
|
||||
Websocket *websocket.Conn
|
||||
}
|
||||
|
||||
type PartyDataRequest struct {
|
||||
Type string `json:"type"`
|
||||
UUID string `json:"uuid"`
|
||||
}
|
||||
|
||||
type PartyDataResponse struct {
|
||||
Data []struct {
|
||||
Color int `json:"color"`
|
||||
FadingColor int `json:"fadingColor"`
|
||||
HlaForceIdentifier int `json:"hlaForceIdentifier"`
|
||||
ID int `json:"id"`
|
||||
IsUsedBy bool `json:"isUsedBy"`
|
||||
Name string `json:"name"`
|
||||
RoleID int `json:"roleId"`
|
||||
} `json:"data"`
|
||||
Type string `json:"type"`
|
||||
UUID string `json:"uuid"`
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package serviceportsinformation
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
)
|
||||
|
||||
func (c *ServicePortsInformation) GetPorts() (map[string]int, []int) {
|
||||
req := ServicePortsInformationRequest{Type: "servicePortsInformationRequest", UUID: "UUID"}
|
||||
|
||||
c.Websocket.WriteJSON(req)
|
||||
|
||||
var res ServicePortsInformationResponse
|
||||
err := c.Websocket.ReadJSON(&res)
|
||||
if err != nil {
|
||||
log.Fatal("ServicePortsInformationRequest - Error getting service ports information:", err)
|
||||
}
|
||||
|
||||
var exerciseServicePortMap map[string]int = make(map[string]int)
|
||||
for _, exercise := range res.Data {
|
||||
for _, service := range exercise.Services {
|
||||
for _, port := range service.Ports {
|
||||
log.Printf("%d-%s%d\n", exercise.ExerciseID, service.Name, port.TypeID)
|
||||
exerciseServicePortMap[fmt.Sprintf("%d-%s%d", exercise.ExerciseID, service.Name, port.TypeID)] = port.Number
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var excerciseIds []int
|
||||
for _, exercise := range res.Data {
|
||||
excerciseIds = append(excerciseIds, exercise.ExerciseID)
|
||||
}
|
||||
|
||||
c.Websocket.Close()
|
||||
|
||||
return exerciseServicePortMap, excerciseIds
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package serviceportsinformation
|
||||
|
||||
import (
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
type ServicePortsInformation struct {
|
||||
Websocket *websocket.Conn
|
||||
}
|
||||
|
||||
type ServicePortsInformationRequest struct {
|
||||
Type string `json:"type"`
|
||||
UUID string `json:"uuid"`
|
||||
}
|
||||
|
||||
type ServicePortsInformationResponse struct {
|
||||
Data []struct {
|
||||
ExerciseID int `json:"exerciseId"`
|
||||
ReplayPartitionID int `json:"replayPartitionId"`
|
||||
Services []struct {
|
||||
Name string `json:"name"`
|
||||
NameAbbrevation string `json:"nameAbbrevation"`
|
||||
Ports []struct {
|
||||
Number int `json:"number"`
|
||||
Properties string `json:"properties"`
|
||||
TypeID int `json:"typeId"`
|
||||
} `json:"ports"`
|
||||
} `json:"services"`
|
||||
} `json:"data"`
|
||||
Type string `json:"type"`
|
||||
UUID string `json:"uuid"`
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
package typedata
|
||||
|
||||
import (
|
||||
"log"
|
||||
)
|
||||
|
||||
func (c *TypeData) GetTypeData() TypeDataResponse {
|
||||
req := TypeDataRequest{Type: "baseDlRequest", UUID: "UUID"}
|
||||
|
||||
c.Websocket.WriteJSON(req)
|
||||
|
||||
var res TypeDataResponse
|
||||
for {
|
||||
err := c.Websocket.ReadJSON(&res)
|
||||
if err != nil {
|
||||
log.Fatal("TypeDataRequest - Error getting type data:", err)
|
||||
}
|
||||
|
||||
if res.Type == "typeBaseData" {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
c.Websocket.Close()
|
||||
|
||||
return res
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
package typedata
|
||||
|
||||
import (
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
type TypeData struct {
|
||||
Websocket *websocket.Conn
|
||||
}
|
||||
|
||||
type TypeDataRequest struct {
|
||||
Type string `json:"type"`
|
||||
UUID string `json:"uuid"`
|
||||
}
|
||||
|
||||
type TypeDataResponse struct {
|
||||
Data []struct {
|
||||
DisEntities string `json:"disEntities"`
|
||||
HierarchyLevel int `json:"hierarchyLevel"`
|
||||
ID int `json:"id"`
|
||||
IsDeleted bool `json:"isDeleted"`
|
||||
Name string `json:"name"`
|
||||
Properties string `json:"properties"`
|
||||
RadioCount int `json:"radioCount"`
|
||||
Symbol string `json:"symbol"`
|
||||
TypeClassID int `json:"typeClassId"`
|
||||
EquipmentTypeID int `json:"equipmentTypeId,omitempty"`
|
||||
} `json:"data"`
|
||||
Type string `json:"type"`
|
||||
UUID string `json:"uuid"`
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package websocket
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func (s *Server) exerciseIds(w http.ResponseWriter, r *http.Request) {
|
||||
c, err := s.Upgrader.Upgrade(w, r, nil)
|
||||
if err != nil {
|
||||
log.Println("Websocket Server - Error upgrading connection:", err)
|
||||
return
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
c.WriteJSON(&s.ExerciseIds)
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package websocket
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
participanthandler "legatus-rme/backend/participantHandler"
|
||||
partydata "legatus-rme/backend/partyData"
|
||||
serviceportsinformation "legatus-rme/backend/servicePortsInformation"
|
||||
typedata "legatus-rme/backend/typeData"
|
||||
"log"
|
||||
"net/url"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
func OpenWebsocket(websocketType string, port []int) interface{} {
|
||||
var sockets []*websocket.Conn
|
||||
for _, port := range port {
|
||||
u := url.URL{Scheme: "ws", Host: fmt.Sprintf("localhost:%d", port)}
|
||||
|
||||
socket, _, err := websocket.DefaultDialer.Dial(u.String(), nil)
|
||||
if err != nil {
|
||||
log.Fatal("Error connecting to websocket:", err)
|
||||
}
|
||||
|
||||
sockets = append(sockets, socket)
|
||||
}
|
||||
|
||||
switch websocketType {
|
||||
case "servicePortsInformation":
|
||||
return serviceportsinformation.ServicePortsInformation{Websocket: sockets[0]}
|
||||
case "typeBaseData":
|
||||
return typedata.TypeData{Websocket: sockets[0]}
|
||||
case "partyBaseData":
|
||||
return partydata.PartyData{Websocket: sockets[0]}
|
||||
case "participantHandler":
|
||||
return participanthandler.ParticipantHandler{ParticipantManagement: sockets[0], MariusSocket: sockets[1]}
|
||||
default:
|
||||
log.Fatal("Invalid websocket type")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package websocket
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
func OpenWebsocketServer(s Server) {
|
||||
s.Upgrader = &websocket.Upgrader{
|
||||
CheckOrigin: func(r *http.Request) bool { return true },
|
||||
}
|
||||
|
||||
http.HandleFunc("/ports", s.ports)
|
||||
http.HandleFunc("/exerciseIds", s.exerciseIds)
|
||||
http.HandleFunc("/partyData", s.partyData)
|
||||
http.HandleFunc("/participantHandler", s.participantHandler)
|
||||
log.Fatal("Websocket Server - Error while serving websockt server:", http.ListenAndServe("localhost:6969", nil))
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
package websocket
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
participanthandler "legatus-rme/backend/participantHandler"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"github.com/go-playground/validator/v10"
|
||||
)
|
||||
|
||||
func (s *Server) participantHandler(w http.ResponseWriter, r *http.Request) {
|
||||
c, err := s.Upgrader.Upgrade(w, r, nil)
|
||||
if err != nil {
|
||||
log.Println("Websocket Server - Error upgrading connection:", err)
|
||||
return
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
for {
|
||||
var req ParticipantHandlerWebsocketRequestWithoutData
|
||||
if err := c.ReadJSON(&req); err != nil {
|
||||
c.WriteJSON(&ErrorWebsocketResponse{
|
||||
Error: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if req.Type != "getAllParticipants" {
|
||||
req2 := ParticipantHandlerWebsocketRequest(req)
|
||||
err = validator.New().Struct(req2)
|
||||
if err != nil {
|
||||
c.WriteJSON(&ErrorWebsocketResponse{
|
||||
Error: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
} else {
|
||||
err = validator.New().Struct(req)
|
||||
if err != nil {
|
||||
c.WriteJSON(&ErrorWebsocketResponse{
|
||||
Error: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
participantHandlerByExcersiseId participanthandler.ParticipantHandler
|
||||
ok bool
|
||||
)
|
||||
if participantHandlerByExcersiseId, ok = s.ParticipantHandlers[req.ExcerciseId]; !ok {
|
||||
participantHandlerByExcersiseId = OpenWebsocket("participantHandler", []int{s.ExerciseServicePortMap[fmt.Sprintf("%d-ParticipantManagement1", req.ExcerciseId)], req.ExcerciseId * 102}).(participanthandler.ParticipantHandler)
|
||||
}
|
||||
|
||||
switch req.Type {
|
||||
case "checkIfParticipantIsValid":
|
||||
participantData := participantHandlerByExcersiseId.CheckIfParticipantIsValid(req.ParticipantData.ParticipantId)
|
||||
c.WriteJSON(&participantData)
|
||||
continue
|
||||
case "getAllParticipants":
|
||||
participantData := participantHandlerByExcersiseId.GetAllParticipants()
|
||||
c.WriteJSON(&participantData)
|
||||
continue
|
||||
case "changeAbcProtection":
|
||||
if err := participantHandlerByExcersiseId.ChangeAbcProtection(req.ParticipantData.ParticipantId, req.ParticipantData.AbcProtection); err != nil {
|
||||
c.WriteJSON(&ErrorWebsocketResponse{
|
||||
Error: err.Error(),
|
||||
})
|
||||
}
|
||||
c.WriteJSON(req.ParticipantData)
|
||||
continue
|
||||
case "changePosition":
|
||||
if err := participantHandlerByExcersiseId.ChangePosition(req.ParticipantData.ParticipantId, &participanthandler.GeoPosition{
|
||||
Lat: req.ParticipantData.Positions[0].Lat,
|
||||
Lng: req.ParticipantData.Positions[0].Lng,
|
||||
}); err != nil {
|
||||
c.WriteJSON(&ErrorWebsocketResponse{
|
||||
Error: err.Error(),
|
||||
})
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
package websocket
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
partydata "legatus-rme/backend/partyData"
|
||||
"log"
|
||||
"net/http"
|
||||
"slices"
|
||||
|
||||
"github.com/go-playground/validator/v10"
|
||||
)
|
||||
|
||||
func paintIt(color int) string {
|
||||
hex := fmt.Sprintf("%06x", color+16777216)
|
||||
return "#" + hex
|
||||
}
|
||||
|
||||
func (s *Server) partyData(w http.ResponseWriter, r *http.Request) {
|
||||
c, err := s.Upgrader.Upgrade(w, r, nil)
|
||||
if err != nil {
|
||||
log.Println("Websocket Server - Error upgrading connection:", err)
|
||||
return
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
for {
|
||||
var req PartyDataWebsocketRequest
|
||||
if err := c.ReadJSON(&req); err != nil {
|
||||
c.WriteJSON(&ErrorWebsocketResponse{
|
||||
Error: err.Error(),
|
||||
})
|
||||
}
|
||||
|
||||
err = validator.New().Struct(req)
|
||||
if err != nil {
|
||||
c.WriteJSON(&ErrorWebsocketResponse{
|
||||
Error: err.Error(),
|
||||
})
|
||||
}
|
||||
|
||||
if slices.Contains(s.ExerciseIds, req.ExcerciseId) {
|
||||
partyDataSocket := OpenWebsocket("partyBaseData", []int{s.ExerciseServicePortMap[fmt.Sprintf("%d-ParticipantManagement1", req.ExcerciseId)]}).(partydata.PartyData)
|
||||
partyData := partyDataSocket.GetPartyData()
|
||||
|
||||
var res []PartyDataWebsocketResponse
|
||||
for _, party := range partyData.Data {
|
||||
if party.ID == 0 || party.ID == 1 {
|
||||
continue
|
||||
}
|
||||
|
||||
colorInHex := paintIt(party.Color)
|
||||
|
||||
res = append(res, PartyDataWebsocketResponse{
|
||||
Id: party.ID,
|
||||
Party: party.Name,
|
||||
Color: colorInHex,
|
||||
})
|
||||
}
|
||||
|
||||
c.WriteJSON(&res)
|
||||
} else {
|
||||
c.WriteJSON(&ErrorWebsocketResponse{
|
||||
Error: "Invalid exerciseId",
|
||||
})
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
package websocket
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func (s *Server) ports(w http.ResponseWriter, r *http.Request) {
|
||||
c, err := s.Upgrader.Upgrade(w, r, nil)
|
||||
if err != nil {
|
||||
log.Println("Websocket Server - Error upgrading connection:", err)
|
||||
return
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
c.WriteJSON(&s.ExerciseServicePortMap)
|
||||
}
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
package websocket
|
||||
|
||||
import (
|
||||
participanthandler "legatus-rme/backend/participantHandler"
|
||||
typedata "legatus-rme/backend/typeData"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
type Server struct {
|
||||
ExerciseIds []int
|
||||
ExerciseServicePortMap map[string]int
|
||||
TypeData typedata.TypeDataResponse
|
||||
ParticipantHandlers map[int]participanthandler.ParticipantHandler
|
||||
Upgrader *websocket.Upgrader
|
||||
}
|
||||
|
||||
type PartyDataWebsocketResponse struct {
|
||||
Id int `json:"id"`
|
||||
Party string `json:"party"`
|
||||
Color string `json:"color"`
|
||||
}
|
||||
|
||||
type PartyDataWebsocketRequest struct {
|
||||
ExcerciseId int `json:"excerciseId" validate:"required"`
|
||||
}
|
||||
|
||||
type ParticipantHandlerWebsocketRequestWithoutData struct {
|
||||
Type string `json:"type" validate:"required"`
|
||||
ExcerciseId int `json:"excerciseId" validate:"required"`
|
||||
ParticipantData struct {
|
||||
ParticipantId int `json:"participantId"`
|
||||
Positions []struct {
|
||||
Lat float64 `json:"lat"`
|
||||
Lng float64 `json:"lng"`
|
||||
} `json:"positions"`
|
||||
AbcProtection bool `json:"abcProtection"`
|
||||
} `json:"participantData"`
|
||||
}
|
||||
|
||||
type ParticipantHandlerWebsocketRequest struct {
|
||||
Type string `json:"type" validate:"required"`
|
||||
ExcerciseId int `json:"excerciseId" validate:"required"`
|
||||
ParticipantData struct {
|
||||
ParticipantId int `json:"participantId" validate:"required"`
|
||||
Positions []struct {
|
||||
Lat float64 `json:"lat"`
|
||||
Lng float64 `json:"lng"`
|
||||
} `json:"positions"`
|
||||
AbcProtection bool `json:"abcProtection"`
|
||||
} `json:"participantData" validate:"required"`
|
||||
}
|
||||
|
||||
type ErrorWebsocketResponse struct {
|
||||
Error string `json:"error"`
|
||||
}
|
||||
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