Compare commits

...

10 Commits

Author SHA1 Message Date
Henry Winkel
4466dc4331 ADD: added own ship icon and adaption for cms result 2024-03-14 18:35:52 +01:00
Henry Winkel
b17c62ae09 ADD: added Debug perceived tracklist and minimap 2024-03-07 18:36:57 +01:00
Henry Winkel
5a59f87f13 ADD: added debug webseit und fixed some bugs 2024-02-15 18:39:44 +01:00
Henry Winkel
f9e5916081 ADD: addded a tracklist sanitizer 2023-12-20 15:44:26 +01:00
hwinkel
8a4795296e ADD: added request for tracklist for entity 2023-11-10 13:28:11 +01:00
Henry Winkel
8f703dd1c7 ADD: added TRacklistClass and track class 2023-11-09 17:19:45 +01:00
Henry Winkel
46f041d58e ADD: added default scenario button 2023-11-06 17:44:01 +01:00
Henry Winkel
3e95bc2633 ADD: added react-router and updated structure of app 2023-11-03 12:35:43 +01:00
Henry Winkel
f926604c7f ADD: add possibility to add and delete entities 2023-11-02 18:03:14 +01:00
Henry Winkel
c7140e0653 ADD: rounded some numbers and changed that only strings are sended to the ws 2023-11-01 09:44:23 +01:00
53 changed files with 2757 additions and 1169 deletions

14
webapp/jsconfig.json Normal file
View File

@@ -0,0 +1,14 @@
{
"compilerOptions": {
"module": "ESNext",
"moduleResolution": "Node",
"target": "ES2020",
"jsx": "react",
"strictNullChecks": true,
"strictFunctionTypes": true
},
"exclude": [
"node_modules",
"**/node_modules/*"
]
}

1146
webapp/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -7,17 +7,20 @@
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"bootstrap": "^5.3.2",
"leaflet": "^1.9.4",
"leaflet-contextmenu": "^1.4.0",
"milsymbol": "^2.2.0",
"react": "^18.2.0",
"react-bootstrap": "^2.9.0",
"react-container-dimensions": "^1.4.1",
"react-dom": "^18.2.0",
"react-hook-form": "^7.46.2",
"react-leaflet": "^4.2.1",
"react-router-dom": "^6.18.0",
"react-scripts": "^5.0.1",
"react-use-websocket": "^4.3.1",
"sass": "^1.66.1",
"typescript": "^5.2.2",
"usehooks-ts": "^2.9.1",
"web-vitals": "^2.1.4",
"websocket": "^1.0.34"
},

View File

@@ -0,0 +1,72 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
id="svg2"
height="592.92773"
width="592.92773"
version="1.1"
inkscape:version="0.91 r13725"
sodipodi:docname="NATO Map Symbol - Friendly Sea - Own Ship.svg">
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1536"
inkscape:window-height="801"
id="namedview65"
showgrid="false"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
inkscape:zoom="1"
inkscape:cx="68.45469"
inkscape:cy="233.21213"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="svg2"
inkscape:object-nodes="true" />
<defs
id="defs4" />
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<path
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:none;stroke:#00c8ff;stroke-width:8;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate"
id="path2988"
d="m 514.47312,296.57436 a 218.00928,218.00928 0 0 1 -436.018504,0 218.00928,218.00928 0 1 1 436.018504,0 z"
inkscape:connector-curvature="0" />
<path
style="fill:none;fill-rule:evenodd;stroke:#00c8ff;stroke-width:8;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 296.46387,78.454681 0,436.018369"
id="path4136"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cc" />
<path
style="fill:none;fill-rule:evenodd;stroke:#00c8ff;stroke-width:8;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 514.47305,296.46387 -436.018363,0"
id="path4136-5"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cc" />
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@@ -1,205 +1,39 @@
// App.js
import React, { Component } from "react";
import "./App.css";
// import { connect, sendMsg } from "./components/api/index";
import Header from './components/Header';
import Controls from "./components/control/controls";
// import ChatHistory from "./components/ChatHistory/ChatHistory.jsx";
import OpenSeaMap from "./components/OpenSeaMap/OpenSeaMap";
import Header from "./components/Header/index"
import SimControl from './components/SimControl'
import {w3cwebsocket as W3CWebSocket} from "websocket"
import { BrowserRouter, Routes, Route } from "react-router-dom";
import Gateway from "./components/Gateway";
import Database from "./components/Database";
import Scenarios from "./components/Scenarios";
import Settings from "./components/Settings";
import Debug from "./components/Debug";
export const defaultLocale = "en-US";
const config = {
// apiUrl: process.env.REACT_APP_WEBAPP_WS_URL,
apiUrl: "192.168.3.13",
apiProt: 30747
}
// const config = {
// // apiUrl: process.env.REACT_APP_WEBAPP_WS_URL,
// apiUrl: "localhost",
// apiProt: 9999
// }
const client = new W3CWebSocket("ws://"+config.apiUrl+":"+config.apiProt+"/");
class App extends Component {
state = {
Entities: [],
EntityOnFocus: undefined,
PositionClicked: undefined
}
componentDidMount() {
console.log(config.apiUrl);
client.onopen = () => {
console.log("Websocket Client for Map Connected");
};
client.onmessage = (message) => {
const dataFromServer = JSON.parse(message.data);
// console.log('reply: ', dataFromServer);
if(dataFromServer.Data === "COP")
{
// console.log(this.state.EntityOnFocus);
var tmp = []
if(this.state.Entities.length !== 0){
dataFromServer.Entities.forEach((elementFromWS, indexWS) => {
this.state.Entities.forEach((elementStored, indexStore) => {
if(elementFromWS.id === elementStored.id)
{
if(elementStored.onFocus !==true | elementStored.onFocus === undefined)
{
tmp.push(elementFromWS);
}else
{
tmp.push(elementStored);
}
}
});
});
}else{
tmp = dataFromServer.Entities;
}
// console.log(tmp);
this.setState((state) => ({
// Entities: structuredClone(dataFromServer.Entities)
Entities: structuredClone(tmp)
})
)
}
}
/// interval for updates
setInterval(() => {
this.updateEntities();
}, 2000);
}
updateEntities()
{
var msg =
{
Type: "Request",
Data: "COP"
}
client.send(JSON.stringify(msg));
}
getEntityFromID(Entities, SelectedEntity)
{
// console.log(Entities);
}
setEntityOnFocus(Entity)
{
if(Entity == undefined)
{
this.resetEntityOnFocus();
return;
}
this.getEntityFromID(this.state.Entities,Entity)
console.log(Entity);
this.setState({
EntityOnFocus: Entity
});
this.state.Entities.forEach((element, index) => {
// console.log(element);
if(element.id === Entity.EntityID)
{
var tmpList = structuredClone(this.state.Entities);
tmpList[index].onFocus = true;
if(Entity.NewPos !== undefined)
{
tmpList[index].Position = Entity.NewPos
}
this.setState({
Entities: structuredClone(tmpList)
});
if(Entity.dragged !== undefined)
{
if(Entity.dragged === true)
{
tmpList[index].dragged = true;
}
}
console.log(tmpList[index]);
}
});
}
resetEntityOnFocus()
{
if(this.state.EntityOnFocus !== undefined)
{
this.state.Entities.map((element ,index) => {
if(element.id === this.state.EntityOnFocus.EntityID)
{
this.state.Entities[index].onFocus = false;
}
})
console.log(this.state.Entities)
this.setState({
EntityOnFocus: undefined
})
}
}
setFocusPosition(props)
{
// console.log(Entity);
this.setState({
PositionClicked: props
});
}
Functions = {
updateEntities: this.updateEntities.bind(this),
resetEntityOnFocus: this.resetEntityOnFocus.bind(this)
}
render() {
const {name} = this.state;
const App = () => {
return (
<div className="App">
<Header />
<div className="Content">
<><Header />
<BrowserRouter>
<Routes>
<Route path="/" element={<SimControl />} />
<Route path="/debug" element={<Debug />} />
<Controls Functions= {this.Functions} Client= {client} Entities= {this.state.Entities} updateEntities = {this.updateEntities} EntityOnFocus = {this.state.EntityOnFocus} PositionClicked = {this.state.PositionClicked} />
<Route path="/scenarios" element={<Scenarios />} />
<Route path="/database" element={<Database />} />
<Route path="/gateway" element={<Gateway />} />
<Route path="/settings" element={<Settings />} />
<OpenSeaMap Entities= {this.state.Entities} updateEntities = {this.updateEntities} setEntityOnFocus = {this.setEntityOnFocus.bind(this)} setFocusPosition = {this.setFocusPosition.bind(this)}/>
</div>
</div>
</Routes>
</BrowserRouter></>
);
}
}
export default App;

View File

@@ -0,0 +1,12 @@
import React from "react";
function Database()
{
return(
<p>Database</p>
);
}
export default Database;

View File

@@ -0,0 +1,3 @@
import Database from "./Database.jsx";
export default Database;

View File

@@ -0,0 +1,135 @@
import React from "react";
import {w3cwebsocket as W3CWebSocket} from "websocket"
import PerceivedTruth from "./components/perceivedTruth/perceivedTruth.jsx";
import "./Debug.scss"
import { useEffect, useState } from 'react'
import { getCOP,loadAutoraster } from "../api/APICalls";
import TrackListClass from "../SimControl/Tracklist.tsx";
const config = {
// apiUrl: process.env.REACT_APP_WEBAPP_WS_URL,
apiUrl: "192.168.3.13",
apiProt: 30747
}
let client = null;
// let client = new W3CWebSocket("ws://"+config.apiUrl+":"+config.apiProt+"/");
function Scenarios()
{
const [trackList, settrackList] = useState([]);
const [entityTracklist, setEntityTracklist] = useState([]);
const [clientConnected, setclientConnected] = useState(false);
// const [client, setclient] = useState(W3CWebSocket);
React.useEffect(() => {
client = new W3CWebSocket("ws://"+config.apiUrl+":"+config.apiProt+"/");
client.onopen = () => {
console.log("Websocket Client for Debug Component");
setclientConnected(true)
};
console.log(`initializing interval`);
const interval = setInterval(() => {
updateEntities();
}, 1000);
return () => {
console.log(`clearing interval`);
clearInterval(interval);
client.close();
client = null;
};
}, []); // has no dependency - this will be called on-component-mount
useEffect(() => {
client.onmessage = (message) => {
if(message.data !== "null")
{
// console.log(message.data);
const dataFromServer = JSON.parse(message.data);
if(dataFromServer.Data === "COP")
{
settrackList(dataFromServer.Entities);
}else
{
// console.log(dataFromServer);
setEntityTracklist(dataFromServer)
}
}
else{
console.log(message);
}
}
});
function updateEntities()
{
// var msg =
// {
// Type: "Request",
// Data: "COP"
// }
client.send(getCOP());
}
const sendSampleMessage = () =>
{
var msg =
{
Data: "TEST",
Type: "Save",
}
console.log(msg);
client.send(JSON.stringify(msg));
}
const startSampleScenario = () =>
{
client.send(loadAutoraster());
}
return(
<>
{/* <div><button onClick={sendSampleMessage}> Save Scenario</button></div>
<div><button onClick={startSampleScenario}> Sample Scenario</button></div> */}
<div className="Container">
<div className="InfoPane" >
<p>Debug</p>
<PerceivedTruth Client={client} EntityTracklist={entityTracklist} Tracklist= {trackList} />
</div>
</div>
</>
);
}
export default Scenarios;

View File

@@ -0,0 +1,9 @@
.Container
{
display: flex;
}
.InfoPane{
width: 100%;
padding-left: 1rem
}

View File

@@ -0,0 +1,185 @@
import { useLeafletContext } from '@react-leaflet/core'
import { MapContainer, TileLayer, Marker,Tooltip} from 'react-leaflet'
import L, { Icon, icon } from 'leaflet'
import { useEffect, useState,useRef, useCallback,useMemo } from 'react'
import "./map.scss"
import { OwnShipIcon, createIcon } from '../../../../SimControl/components/OpenSeaMap/icon'
const zoom = 9
function DisplayPosition({ map,center }) {
const [position, setPosition] = useState(() => map.getCenter())
// const onClick = useCallback(() => {
// map.setView(center, zoom)
// }, [map])
const onMove = useCallback(() => {
setPosition(map.getCenter())
}, [map])
useEffect(() => {
map.setView(center, zoom)
}, [center, map])
useEffect(() => {
map.on('move', onMove)
return () => {
map.off('move', onMove)
}
}, [map, onMove])
return (
<p>
latitude: {position.lat.toFixed(4)}, longitude: {position.lng.toFixed(4)}{' '}
{/* <button onClick={onClick}>reset</button> */}
</p>
)
}
function MyMap({center,Tracklist}) {
const [map, setMap] = useState(null)
const [mapcenter, setMapcenter] = useState([51.505, -0.09])
const [isFocused, setIsFocused] = useState(false)
const [trackList,setTracklist] = useState([])
// const OwnShipIcon = L.icon({
// iconUrl: 'Own_Ship.svg',
// iconSize: [50, 50], // size of the icon
// });
useEffect(() => {
// console.log(center);
if(center.length !== 0)
{
setMapcenter(center);
setIsFocused(true);
}
}, [center]);
useEffect(() => {
// console.log(Tracklist);
// makeList(Tracklist.Tracks)
var tracks = Tracklist.Tracks
var list = [];
// var Symbol = {
// ID: "1234",
// Name:"Own Ship",
// Position: mapcenter ,
// IconData: OwnShipIcon,
// }
// list.push(Symbol);
// setTracklist(list);
if(tracks === undefined)
{
return;
}
tracks.forEach(element => {
console.log(element)
var Symbol = {
ID: element.ID,
Name:element.Name,
Position: [element.Position[0], element.Position[1]] ,
IconData: createIcon(element.Type, element.Side),
}
list.push(Symbol);
});
// console.log(list);
setTracklist(list);
}, [Tracklist, mapcenter]);
// function makeList(tracks)
// {
// }
function makeIcons()
{
if(trackList === undefined)
{
return;
}
var list =[];
trackList.forEach(element => {
list.push(
<Marker name={element.Name} key={element.ID} icon={element.IconData} position={element.Position} >
<Tooltip direction='bottom' offset={[0,10]} opacity={1} permanent>
<span>{element.Name}</span>
</Tooltip>
</Marker>)
})
return(
list
)
}
const displayMap = useMemo(
() => (
<MapContainer
center={mapcenter}
zoom={zoom}
attributionControl={false}
// scrollWheelZoom={false}
ref={setMap}>
<TileLayer
attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
{isFocused ? <Marker icon={OwnShipIcon} position={mapcenter} >
<Tooltip direction='bottom' offset={[0,13]} opacity={1} permanent>
<span>{"Own Ship"}</span>
</Tooltip>
</Marker> :null }
{ makeIcons()}
{
}
</MapContainer>
),
[isFocused, mapcenter, trackList],
)
return (
<div className='Smallapmap'>
{map ? <DisplayPosition map={map} center={mapcenter} /> : null}
{displayMap }
</div>
)
}
export default MyMap;

View File

@@ -0,0 +1,9 @@
.Smallapmap {
// display: flex;
// padding-top: 10px;
height: 500px;
width: 100%;
// padding-left: 20%;
display: inline-flex;
// height: 80%;
}

View File

@@ -0,0 +1,66 @@
.App {
display: flex;
text-align: center;
}
.App-logo {
height: 40vmin;
pointer-events: none;
}
@media (prefers-reduced-motion: no-preference) {
.App-logo {
animation: App-logo-spin infinite 20s linear;
}
}
.App-header {
background-color: #282c34;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: calc(10px + 2vmin);
color: white;
}
.App-link {
color: #61dafb;
}
@keyframes App-logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
.leaflet-container {
width: 70vw;
height: 100vh;
}
.sidebar {
box-sizing: border-box;
padding: 2em;
width: 30vw;
}
.sidebar h2,
.sidebar p,
.sidebar ul {
text-align: left;
}
.sidebar h2 {
margin-top: 0;
}
.sidebar button {
width: 100%;
font-weight: bold;
padding: .8em 1em;
}

View File

@@ -0,0 +1,131 @@
import { useEffect, useState } from 'react'
import { getWSTestMessage ,getTracklist } from '../../../api/APICalls';
import MyMap from "./map/map.jsx"
import "./perceivedTruth.scss"
import TrackList from './tracklist/tracklist.jsx';
function PerceivedTruth(props)
{
const [selectedOption, setSelectedOption] = useState('option1')
const [selectedOptionFull, setSelectedOptionFull] = useState({
ID : null,
Label:"",
Position: []
})
const [selectOptions, setSelectOptions] = useState([])
const [trackList, settrackList] = useState([]);
const [client, setclient] = useState()
const handleSubmit = (event) => {
if(selectedOption !== "option1" && selectedOption !== "" )
{
let updatedValue = getAttributesForID(selectedOption);
setSelectedOptionFull(selectedOptionFull => ({
...selectedOptionFull,
...updatedValue
}));
client.send(getTracklist(selectedOption));
event.preventDefault()
console.log(`Submitted selected option: ${selectedOption}`)
}
}
function getAttributesForID(ID)
{
let attributes = {
ID: undefined,
Name: "",
Position : []
};
selectOptions.forEach(element => {
// console.log(element);
if(element.value === ID)
{
attributes.ID = ID;
attributes.Name = element.label;
attributes.Position = element.position;
}
})
return attributes;
}
function prepareTracklistForSelect(Tracklist)
{
if(Tracklist!== undefined)
{
var options = [];
Tracklist.forEach(element => {
var item =
{
value : element.id,
label : element.Name,
position: element.Position
};
options.push(item);
});
setSelectOptions(options);
}
}
useEffect(() => {
//Runs on the first render
//And any time any dependency value changes
console.log(props.EntityTracklist);
setclient(props.Client);
settrackList(props.Tracklist);
prepareTracklistForSelect(trackList);
}, [props, trackList]);
return(
<div className='box'>
<div className='EntityTracklist'>
<form onSubmit={handleSubmit}>
<label htmlFor="my-select">Select Track:</label>
<select
id="my-select"
value={selectedOption}
onChange={(event) => setSelectedOption(event.target.value)}
>
<option> </option>
{selectOptions.map((option, index) => (
<option key={index} value={option.value} label={option.label} />
))}
</select>
<button type="submit">Submit</button>
</form>
<p>{ selectedOptionFull.Label}</p>
<TrackList Tracklist ={props.EntityTracklist} />
</div>
<div className='SideMap'>
<MyMap center={selectedOptionFull.Position} Tracklist ={props.EntityTracklist} />
</div>
</div>
)}
export default PerceivedTruth;

View File

@@ -0,0 +1,14 @@
.box{
display: flex;
width: 100%;
}
.EntityTracklist
{
width: 50%;
}
.SideMap{
width: 40%;
justify-content: center;
margin: 0 auto;
}

View File

@@ -0,0 +1,97 @@
import React, { useEffect, useState } from 'react'
import "./tracklist.scss"
import round from "../../../../Utils.jsx"
function TrackList(props)
{
const [tracklist, setTracklist] = useState(Array)
useEffect(() => {
setTracklist(Array);
// console.log(props)
}, []);
useEffect(() => {
if(props.Tracklist.Tracks !== undefined)
{
setTracklist(props.Tracklist.Tracks);
}
// console.log(props)
}, [props]);
function MakeSensorList(props)
{
if(props === undefined)
{
return;
}
var list = [];
props.Sensors.forEach(element => {
list.push(<span> {element.Name} Range: {element.Range} NM </span>)
});
return(list);
}
return (
<div>
<div class="divTable">
<div class="divTableBody">
<div class="divTableRow">
<div class="divTableCell">NR</div>
<div class="divTableCell">ID</div>
<div class="divTableCell">Name</div>
<div class="divTableCell">Position</div>
<div class="divTableCell">Bearing</div>
<div class="divTableCell">Range</div>
<div class="divTableCell">Threat</div>
<div class="divTableCell">Sensors</div>
<div class="divTableCell">Status</div>
</div>
{/* {<MakeRow />} */}
{
tracklist.map( (elem,index) => {
if(elem.Position !== undefined)
{
// console.log(elem)
return(
<div class="divTableRow">
<div class="divTableCell">{index}</div>
<div class="divTableCell">{elem.ID}</div>
<div class="divTableCell">{elem.Name}</div>
<div class="divTableCell">{round(elem.Position[0],4)}, {round(elem.Position[1],4)}</div>
<div class="divTableCell">{round(elem.Bearing,2)}</div>
<div class="divTableCell">{round(elem.Distance,2)}</div>
<div class="divTableCell">{elem.Threat}</div>
<div class="divTableCell">{}</div>
<div class="divTableCell">{elem.Status}</div>
{/* <div class="divTableCell">{MakeSensorList(elem) }</div> */}
</div>
)
}
})
}
</div>
</div>
</div>
)}
export default TrackList;

View File

@@ -0,0 +1,30 @@
/* DivTable.com */
.divTable{
display: table;
width: 100%;
}
.divTableRow {
display: table-row;
}
.divTableHeading {
background-color: #EEE;
display: table-header-group;
}
.divTableCell, .divTableHead {
border: 1px solid #999999;
display: table-cell;
padding: 3px 10px;
}
.divTableHeading {
background-color: #EEE;
display: table-header-group;
font-weight: bold;
}
.divTableFoot {
background-color: #EEE;
display: table-footer-group;
font-weight: bold;
}
.divTableBody {
display: table-row-group;
}

View File

@@ -0,0 +1,3 @@
import Debug from "./Debug.jsx";
export default Debug;

View File

@@ -0,0 +1,12 @@
import React from "react";
function Gateway()
{
return(
<p>Gateway</p>
);
}
export default Gateway;

View File

@@ -0,0 +1,3 @@
import Gateway from "./Gateway.jsx";
export default Gateway;

View File

@@ -1,10 +1,40 @@
import React from "react";
import Container from 'react-bootstrap/Container';
import Nav from 'react-bootstrap/Nav';
import Navbar from 'react-bootstrap/Navbar';
import "./header.scss";
import 'bootstrap/dist/css/bootstrap.min.css';
const Header = () => (
<div className="header">
<font className="caption">Cloud Simulator</font>
</div>
function Header() {
return(
<Navbar expand="lg" bg="secondary">
<Container fluid >
<Navbar.Brand href="/"><h2>Cloud Simulator</h2></Navbar.Brand>
<Navbar.Toggle aria-controls="navbarScroll" />
<Navbar.Collapse id="navbarScroll">
<Nav
className="me-auto my-2 my-lg-0"
style={{ maxHeight: '100px' }}
navbarScroll
>
<Nav.Link href="debug">Debug</Nav.Link>
<Nav.Link href="scenarios">Scenarios</Nav.Link>
<Nav.Link href="database">Database</Nav.Link>
<Nav.Link href="gateway">Gateway</Nav.Link>
<Nav.Link href="settings">Settings</Nav.Link>
</Nav>
</Navbar.Collapse>
</Container>
</Navbar>
);
}
export default Header;

View File

@@ -1,211 +0,0 @@
// import { useMapEvents } from 'react-leaflet/hooks'
import React, { Component } from "react";
import { MapContainer, TileLayer,Marker, Popup, Tooltip,useMapEvents,contextmenu } from 'react-leaflet'
import { friend,Hostile, iconShip,createIcon } from "./icon";
import "./OpenSeaMap.scss";
import {w3cwebsocket as W3CWebSocket} from "websocket";
import ContainerDimensions from 'react-container-dimensions';
import {} from "leaflet-contextmenu";
class OpenSeaMap extends Component {
state = {
EntityOnFocus: undefined,
}
updateDimensions() {
const height = window.innerWidth >= 950 ? window.innerHeight : 400
this.setState({ height: height })
}
componentWillMount() {
this.updateDimensions()
}
componentDidMount() {
window.addEventListener("resize", this.updateDimensions.bind(this))
console.log(this.state.EntityOnFocus);
}
componentWillUnmount() {
window.removeEventListener("resize", this.updateDimensions.bind(this))
}
handleClick(e)
{
// console.log(e.target)
// this.setState({ currentPos: e.latlng });
// console.log(e.latlng);
// console.log("hello world");
}
makeIcon(index, entity,props,state)
{
var isOnFocus = false;
if(this.state.EntityOnFocus !== undefined){
// console.log(entity.id+ " " , this.state.EntityOnFocus);
if(entity.id === this.state.EntityOnFocus.EntityID)
{
isOnFocus = true
// if(this.state.EntityOnFocus.NewPos !== undefined)
// {
// entity.pos = this.state.EntityOnFocus.NewPos;
// }
}
}
var icon;
icon = createIcon(entity.Type,entity.Side)
return (
<Marker name={entity.Name} key={index} icon={icon} position={entity.Position} data={entity.id}
draggable={isOnFocus? true : false}
contextmenu={true}
contextmenuWidth={140}
contextmenuItems={[{
text:"Edit",
index:0,
callback: function() {
// this.setState({
// EntityOnFocus:entity.id
// })
var Ent = {
EntityID : entity.id,
NewPos : undefined
}
state.EntityOnFocus = Ent;
props.setEntityOnFocus(Ent)
// console.log(state.EntityOnFocus);
}
}, {
text:"Delete",
// separator: true,
index: 1
}, {
text:"Exit",
callback: function()
{
state.EntityOnFocus = undefined;
props.setEntityOnFocus(undefined);
},
// separator: true,
index: 2
}]}
// contextmenuItems: [{
// text: 'Circle 1',
// callback: function() {
// circleContextClick(circle1);
// }
// }]
eventHandlers={{
click: (e) => {
// console.log(e.target);
// var Ent = {
// EntityID : e.target.options.data,
// NewPos : undefined
// }
// this.setState((state) =>({
// EntityOnFocus: Ent
// }))
// this.props.setEntityOnFocus(Ent);
// this.props.setEntityOnFocus(e.target.options.data);
},
dragend: (e) =>
{
var Ent = {
EntityID : e.target.options.data,
NewPos : [e.target._latlng.lat,e.target._latlng.lng],
dragged: true
}
this.setState((state) =>({
EntityOnFocus: Ent
}))
this.props.setEntityOnFocus(Ent);
// console.log(e.target);
// console.log(e.target._latlng);
},
// contextmenu: (e) =>
// {
// console.log("right click");
// }
}}
// eventHandlers={this.MarkerEventHandler}
>
{/* <Popup>
Omu-Aran the Head Post of Igbomina land,
is a town in the Nigerian state of Kwara.
It originated from Ife and currently the local
government headquarters of Irepodun local government.
</Popup> */}
<Tooltip direction='bottom' offset={[0,13]} opacity={1} permanent>
<span>{entity.Name}</span>
</Tooltip>
</Marker>
);
}
setPos(props)
{
console.log(props);
this.props.setFocusPosition([1,2])
}
render() {
const LocationFinderDummy = (props) => {
const map = useMapEvents({
click(e) {
props.props.setFocusPosition(e.latlng)
// console.log(e.latlng);
},
});
return null;
};
// const positions = this.props.Positions.map((pos, index) => (
// <Marker name={"test"} key={index} position={pos.position} icon= {ship} />
// ));
return (
<div className='map' style={{ height: this.state.height }} >
<MapContainer center={[54, 11]} zoom={6} scrollWheelZoom={true} contextmenu={false}
>
<LocationFinderDummy props ={this.props} />
<TileLayer
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" />
<TileLayer
url="https://tiles.openseamap.org/seamark/{z}/{x}/{y}.png" />
{/* {positions} */}
{this.props.Entities.map((pos, index) => (
this.makeIcon(index,pos,this.props,this.state)
))}
</MapContainer>
</div>
);
}
}
export default OpenSeaMap;

View File

@@ -0,0 +1,57 @@
import React from "react";
import {w3cwebsocket as W3CWebSocket} from "websocket"
function Scenarios()
{
const config = {
// apiUrl: process.env.REACT_APP_WEBAPP_WS_URL,
apiUrl: "192.168.3.13",
apiProt: 30747
}
const client = new W3CWebSocket("ws://"+config.apiUrl+":"+config.apiProt+"/");
client.onmessage = (message) => {
console.log(message);
}
const sendSampleMessage = () =>
{
var msg =
{
Data: "TEST",
Type: "Save",
}
console.log(msg);
client.send(JSON.stringify(msg));
}
const startSampleScenario = () =>
{
var msg =
{
Data: "Scenario",
Type: "AutoRaster",
}
console.log(msg);
client.send(JSON.stringify(msg));
}
return(
<>
<p>Scenarios</p>
<div><button onClick={sendSampleMessage}> Save Scenario</button></div>
<div><button onClick={startSampleScenario}> Sample Scenario</button></div>
</>
);
}
export default Scenarios;

View File

@@ -0,0 +1,3 @@
import Scenarios from "./Scenarios.jsx";
export default Scenarios;

View File

@@ -0,0 +1,12 @@
import React from "react";
function Settings()
{
return(
<p>Settings</p>
);
}
export default Settings;

View File

@@ -0,0 +1,3 @@
import Settings from "./Settings.jsx";
export default Settings;

View File

@@ -0,0 +1,224 @@
import React, { Component } from "react";
import {w3cwebsocket as W3CWebSocket} from "websocket"
// import Header from './components/Header';
import OpenSeaMap from "./components/OpenSeaMap/OpenSeaMap";
import EntityControl from './components/EntityControl/EntityControl';
import Tracklist from './components/Tracklist'
import Equipment from './components/EntityControl/components/Equipment'
import Tab from 'react-bootstrap/Tab';
import Tabs from 'react-bootstrap/Tabs';
import TrackListClass from "./Tracklist.tsx";
import Track from "./Track.tsx";
import './SimControl.scss'
import EntityTrackList from "./components/EntityTrackList";
import { getCOP, getWSTestMessage } from "../api/APICalls";
const config = {
// apiUrl: process.env.REACT_APP_WEBAPP_WS_URL,
apiUrl: "192.168.3.13",
apiProt: 30747
}
const client = new W3CWebSocket("ws://"+config.apiUrl+":"+config.apiProt+"/");
class SimControl extends Component {
state = {
Entities: [],
EntityOnFocus: undefined,
PositionClicked: undefined,
TrackList_: new TrackListClass()
}
componentWillUnmount()
{
// client.close();
}
componentDidMount() {
console.log(config.apiUrl);
client.onopen = () => {
console.log("Websocket Client for Map Connected");
};
client.onmessage = (message) => {
const dataFromServer = JSON.parse(message.data);
// console.log('reply: ', dataFromServer);
if(dataFromServer.Data === "COP" && dataFromServer.Entities !== undefined )
{
var idsFromWs = [];
dataFromServer.Entities.forEach((elementFromWS, indexWS) => {
idsFromWs.push(elementFromWS.id);
});
this.state.TrackList_.checkifTrackIsStillSended(idsFromWs);
// this.state.TrackList_.deleteAll();
var allTracks = this.state.TrackList_.getArrayOfKeys();
dataFromServer.Entities.forEach((elementFromWS, indexWS) => {
// console.log(elementFromWS);
if(this.state.TrackList_.isInList(elementFromWS.id))
{
this.state.TrackList_.updateTrack(elementFromWS);
}else{
this.state.TrackList_.addTrack(structuredClone(new Track(elementFromWS)));
}
});
// console.log(this.state.TrackList_.getSize());
this.setState((state) => ({
// // Entities: structuredClone(dataFromServer.Entities)
// Entities: structuredClone(tmp)
}))
}else if(dataFromServer.Entities === undefined)
{
this.state.TrackList_.deleteAll();
console.log("delting all");
console.log(this.state.TrackList_.getSize());
}
}
/// interval for updates
setInterval(() => {
this.updateEntities();
}, 2000);
}
isStored(id) {
var found = false;
this.state.Entities.forEach((elementStored, indexStore) => {
if (id === elementStored.id)
{
found = true;
}
});
return found;
}
updateEntities()
{
// var msg =
// {
// Type: "Request",
// Data: "COP"
// }
client.send(getCOP());
}
getEntityFromID(Entities, SelectedEntity)
{
// console.log(Entities);
}
setEntityOnFocus(Entity)
{
console.log(Entity);
this.setState({
EntityOnFocus: structuredClone(this.state.TrackList_.getTrack(Entity))
})
}
resetEntityOnFocus()
{
if(this.state.EntityOnFocus !== undefined)
{
this.state.Entities.map((element ,index) => {
if(element.id === this.state.EntityOnFocus.EntityID)
{
this.state.Entities[index].onFocus = false;
}
})
console.log(this.state.Entities)
this.setState({
EntityOnFocus: undefined
})
}
}
setFocusPosition(props)
{
// console.log(props);
this.setState({
PositionClicked: props
});
}
Functions = {
updateEntities: this.updateEntities.bind(this),
resetEntityOnFocus: this.resetEntityOnFocus.bind(this),
PositionClicked: this.setFocusPosition.bind(this)
}
render() {
return (
<div className="App">
<div className="Content">
{/* <Controls TrackList = {this.state.TrackList_} Functions= {this.Functions} Client= {client} Entities= {this.state.Entities} updateEntities = {this.updateEntities} EntityOnFocus = {this.state.EntityOnFocus} PositionClicked = {this.state.PositionClicked} /> */}
<div className="controls">
<Tracklist entities= {this.state.TrackList_} />
<Tabs
defaultActiveKey="home"
transition={false}
id="noanim-tab-example"
className="mb-3"
>
<Tab eventKey="home" title="Home">
<EntityControl
Functions = {this.props.Functions}
Client = { client }
Entity = { this.state.EntityOnFocus}
PositionClicked = {this.state.PositionClicked}
/>
</Tab>
<Tab eventKey="Equipment" title="Equipment">
<Equipment Entity ={this.state.EntityOnFocus} />
</Tab>
<Tab eventKey="internalTrack" title="internal Tracks">
Internaltracklist
</Tab>
<Tab eventKey="orders" title="Orders">
Orders
</Tab>
</Tabs>
</div>
<OpenSeaMap Client = {client} TrackListMap = {this.state.TrackList_.getTracks()} TrackList = {this.state.TrackList_} setFocusPosition = {this.setFocusPosition.bind(this)} setEntityOnFocus = {this.setEntityOnFocus.bind(this)} />
</div>
</div>
);
}
}
export default SimControl;

View File

@@ -0,0 +1,6 @@
.controls{
/* display: flex; */
width: 24%;
float: left;
min-width: 400px;
}

View File

@@ -0,0 +1,38 @@
class Track{
Id : string;
Name = String();
Type = String();
Side = String();
Course = Number();
Speed = Number();
External = Boolean();
Height = Number();
Position = [Number(), Number()]
isOnFucus = Boolean();
isOnEdit = Boolean();
constructor(input)
{
if(input.id !== undefined)
{
this.Id = input.id;
}else{
return;
}
this.Name = input.Name;
this.Course = input.Course;
this.Speed = input.Speed;
this.External = input.External;
this.Height = input.Height;
this.Position = input.Position;
this.Type = input.Type;
this.Side = input.Side;
this.isOnFucus = false;
this.isOnEdit = false;
}
};
export default Track

View File

@@ -0,0 +1,118 @@
import { ObjectType } from 'typescript';
import Track from './Track'
class TrackListCLass
{
constructor()
{
this.trackMap = new Map<string, Track>();
}
trackMap: Map<string, Track> ;
getTrack(id :string)
{
return this.trackMap.get(id);
}
isInList(id :string)
{
return this.trackMap.has(id);
}
addTrack(track :Track)
{
this.trackMap.set(track.Id,track);
}
updateTrack(input: { id: string; Name: string; Course: number; Speed: number; External: boolean; Height: number; Position: number[]; Type: string; Side: string; })
{
var Track = this.trackMap.get(input.id);
if(Track === undefined)
{
return;
}
if(Track.isOnEdit === false)
{
Track.Name = input.Name;
Track.Course = input.Course;
Track.Speed = input.Speed;
Track.External = input.External;
Track.Height = input.Height;
Track.Position = input.Position;
Track.Type = input.Type;
Track.Side = input.Side;
}
}
getKeys()
{
return this.trackMap.keys;
}
getSize()
{
return this.trackMap.size;
}
getArrayOfKeys()
{
var Tracks = new Array<string>();
this.trackMap.forEach((val,index) =>
Tracks.push(val.Id)
)
return Tracks;
}
getTracks()
{
var Tracks = new Array<Track>();
this.trackMap.forEach((val,index) =>
Tracks.push(val)
)
return Tracks;
}
getTrackMap()
{
return this.trackMap;
}
deteleTrack(id :string)
{
this.trackMap.delete(id);
}
deleteAll()
{
this.trackMap.clear();
}
checkifTrackIsStillSended(tracksFromWS: Array<string>)
{
this.trackMap.forEach((val,index) => {
if(!tracksFromWS.includes(val.Id))
{
this.deteleTrack(val.Id);
console.log("deleted: " + val.Id);
};
});
}
}
export default TrackListCLass

View File

@@ -4,6 +4,7 @@ import { useForm, SubmitHandler } from "react-hook-form"
import Form from 'react-bootstrap/Form';
import FloatingLabel from 'react-bootstrap/FloatingLabel';
import InputGroup from 'react-bootstrap/InputGroup';
import round from "../../../Utils";
import 'bootstrap/dist/css/bootstrap.min.css';
@@ -14,7 +15,8 @@ import './controls.css'
function EntityControl(props)
{
const RoundPrecision = 2;
const RoundPrecisionPosition = 5;
const [Entity, setEntity] = useState(undefined);
@@ -23,55 +25,98 @@ function EntityControl(props)
const [PositionDragged, setPositionDragged] = useState(false);
// const [Speed, setSpeed] = useState();
const [formData, setFormData] = useState({name: "",course: "" ,speed: 0,position: [0,0],height:0});
const [formData, setFormData] = useState({name: "",course: 0 ,speed: 0,position: [0,0],height:0});
useEffect(() => { // Update the document title using the browser API document.title = `You clicked ${count} times`;
// useEffect(() => { // Update the document title using the browser API document.title = `You clicked ${count} times`;
// console.log(props.Entity);
// if(props.Entity !== undefined)
// {
// setEntity(Entity);
// setFormData({name:props.Entity.Name,course:props.Entity.Course,speed:props.Entity.Speed,position:props.Entity.Position,height:round(props.Entity.Height,RoundPrecision)})
// // // console.log(props.Entity)
// // if(Entity === undefined)
// // {
// // setEntity(props.Entity);
// // setFormData({name:props.Entity.Name,course:props.Entity.Course,speed:props.Entity.Speed,position:props.Entity.Position,height:round(props.Entity.Height,RoundPrecision) })
// // }else if(Entity.id !== props.Entity.id)
// // {
// // setEntity(props.Entity);
// // setFormData({name:props.Entity.Name,course:props.Entity.Course,speed:props.Entity.Speed,position:props.Entity.Position,height:round(props.Entity.Height,RoundPrecision)})
// // console.log("new entity")
// // } else if(Entity.id === props.Entity.id && Entity.Position !== props.Entity.Position)
// // {
// // setFormData({...formData,position:props.Entity.Position,height:round(props.Entity.Height,RoundPrecision)})
// // setPositionDragged(true);
// // console.log("dragged");
// // }
// // setFormData({name:props.Entity.Name,course:props.Entity.Course,speed:props.Entity.Speed,position:props.Entity.Position})
// }
// },[Entity, props.Entity]);
useEffect(() => {
// console.log(props.Entity);
// console.log(Entity);
if(props.Entity !== undefined)
{
console.log(props.Entity)
setEntity(structuredClone(props.Entity));
if(Entity === undefined)
{
setEntity(props.Entity);
setFormData({name:props.Entity.Name,course:props.Entity.Course,speed:props.Entity.Speed,position:props.Entity.Position,height:props.Entity.Height })
setFormData({name:props.Entity.Name,course:props.Entity.Course,speed:props.Entity.Speed,position:props.Entity.Position,height:round(props.Entity.Height,RoundPrecision) })
}else if(Entity.id !== props.Entity.id)
}else if(Entity.Id !== props.Entity.Id)
{
setEntity(props.Entity);
setFormData({name:props.Entity.Name,course:props.Entity.Course,speed:props.Entity.Speed,position:props.Entity.Position,height:props.Entity.Height})
} else if(Entity.id === props.Entity.id && Entity.Position !== props.Entity.Position)
setFormData({name:props.Entity.Name,course:props.Entity.Course,speed:props.Entity.Speed,position:props.Entity.Position,height:round(props.Entity.Height,RoundPrecision)})
console.log("new entity")
} else if(Entity.Id === props.Entity.Id && Entity.Position !== props.Entity.Position)
{
setFormData({...formData,position:props.Entity.Position,height:props.Entity.Height})
// setFormData({...formData,position:props.Entity.Position,height:round(props.Entity.Height,RoundPrecision)})
setFormData({name:props.Entity.Name,course:props.Entity.Course,speed:props.Entity.Speed,position:props.Entity.Position,height:round(props.Entity.Height,RoundPrecision)})
setPositionDragged(true);
console.log("dragged");
}
// setFormData({name:props.Entity.Name,course:props.Entity.Course,speed:props.Entity.Speed,position:props.Entity.Position})
}else{
console.log("reset");
setFormData({name:"",course:"",speed:"",position:["",""],height:"" })
setEntity(undefined);
}
},[props.Entity])
},[props.Entity,props]);
useEffect(() => {
if(props.PositionCliecked !== undefined)
if(props.PositionClicked !== undefined)
{
console.log(props.PositionCliecked)
// setEntity(undefined);
// console.log(props.PositionClicked)
var pos = [0,0];
pos[0] = props.PositionCliecked.lat;
pos[1] = props.PositionCliecked.lng
setFormData({...formData,position:pos});
pos[0] = props.PositionClicked.lat;
pos[1] = props.PositionClicked.lng
setFormData({...formData,name:"",course:0,speed:0,position:pos});
}
},[props.PositionCliecked])
},[props.PositionClicked])
@@ -195,9 +240,9 @@ const handleClick = (e) => {
{
props.Entity.onFocus = undefined;
}
setFormData({name:'',course:'',speed:'',position:['','']})
setFormData({name:'',course:0,speed:0,position:[0,0],height:0})
setEntity(undefined)
props.Functions.resetEntityOnFocus();
// props.Functions.resetEntityOnFocus();
}
@@ -216,16 +261,15 @@ const handleClick = (e) => {
}
}
// const handleChange = (event) => {
// const { name, value } = event.target;
// setFormData((prevFormData) => ({ ...prevFormData, [name]: value }));
// };
const handleSubmit = (e) => {
e.preventDefault();
// console.log(e.target.data);
// console.log(formData)
console.log(Entity)
// console.log(Entity)
if(Entity === undefined)
{
var msg =
@@ -235,8 +279,8 @@ const handleClick = (e) => {
Name: formData.name,
Speed: formData.speed.toString(),
Course: formData.course.toString(),
Position: formData.position,
Height: formData.height,
Position: [formData.position[0].toString(),formData.position[1].toString()],
Height: formData.height.toString(),
SetPosition: true
}
@@ -254,11 +298,11 @@ const handleClick = (e) => {
{
Data: "Entity",
Type: "Update",
ID: Entity.id,
ID: Entity.Id,
Speed: formData.speed.toString(),
Course: formData.course.toString(),
Position: formData.position,
Height: formData.height,
Position: [formData.position[0].toString(),formData.position[1].toString()],
Height: formData.height.toString(),
SetPosition: setPos
}
@@ -274,7 +318,7 @@ const handleClick = (e) => {
return (
<div>
<div><button onClick={ emptyForm}> New</button> </div>
<form onSubmit={handleSubmit}>
<div className="ControlsComponent">
@@ -320,6 +364,7 @@ const handleClick = (e) => {
<InputGroup className="mb-3">
<InputGroup.Text id="basic-addon1">Name</InputGroup.Text>
<Form.Control
required
name="Name"
placeholder="Name"
aria-label="Name"
@@ -353,14 +398,14 @@ const handleClick = (e) => {
<div class="div7">
<div class="input-group mb-3 NumberInputsGroup">
<span class="input-group-text">Lat</span>
<input className="ControlInput NumberInputs" name="Lat" type="text" onChange={handleInput} value={formData.position[0]} />
<input className="ControlInput NumberInputs" name="Lat" type="text" onChange={handleInput} value={round(formData.position[0],RoundPrecisionPosition)} />
</div>
</div>
<div class="div8">
<div class="input-group mb-3 NumberInputsGroup" >
<span class="input-group-text">Lon</span>
{/* <input className="ControlInput" name="Lat" readOnly={true} type="text" onClick={handleClick} value={formData.position[0]} /> */}
<input className="ControlInput NumberInputs" name="Lon" readOnly={true} type="text" onClick={handleClick} onChange={handleInput} value={formData.position[1]}/>
<input className="ControlInput NumberInputs" name="Lon" readOnly={true} type="text" onClick={handleClick} onChange={handleInput} value={round(formData.position[1],RoundPrecisionPosition)}/>
</div>
{/* <div className="flex">
<div className="ControlHeader">Lon:</div>
@@ -378,11 +423,17 @@ const handleClick = (e) => {
</div>
</div>
<div className="TopButtons">
<div><button onClick={ emptyForm}> New</button></div>
<div><button type="button" onClick={resetForm}> Reset</button></div>
<div><button type='submit'>Save</button> </div>
</div>
</div>
</form>
<button onClick={resetForm}> RESET</button>
</div>
);
}

View File

@@ -0,0 +1,60 @@
import React from "react"
import {useState,useEffect, useRef} from 'react';
import 'bootstrap/dist/css/bootstrap.min.css';
import './Equipment.css'
function Equipment(props)
{
const [Entity, setEntity] = useState(undefined);
const [EntitySelected, setEntitySelected] = useState(false)
useEffect(() => {
if(props.Entity !== undefined)
{
setEntity(props.Entity);
setEntitySelected(true);
}else
{
setEntitySelected(false);
}
console.log(props)
},[props.Entity])
return (
<div>
<b>{EntitySelected ? '' : 'no Entity Selected'}</b>
</div>
);
}
export default Equipment;

View File

@@ -0,0 +1,3 @@
import Equipment from "./Equipment";
export default Equipment;

View File

@@ -1,8 +1,3 @@
.controls{
/* display: flex; */
width: 24%;
float: left;
}
.ControlsComponent{
display: grid;
@@ -56,21 +51,13 @@ float: left;
display: contents;
}
/* .parent {
display: grid;
grid-template-columns: repeat(2, 1fr);
grid-template-rows: repeat(3 2em);
grid-column-gap: 0px;
grid-row-gap: 0px;
.TopButtons
{
width: 100%;
justify-content: space-evenly;
display: flex;
}
.div1 { grid-area: 1 / 1 / 2 / 3; }
.div2 { grid-area: 2 / 1 / 3 / 2; }
.div3 { grid-area: 2 / 2 / 3 / 3; }
.div4 { grid-area: 3 / 2 / 4 / 3; }
.div5 { grid-area: 3 / 1 / 4 / 2; } */
.parent {
display: grid;
grid-template-columns: repeat(3, 2fr);

View File

@@ -0,0 +1,100 @@
import React from 'react';
import './controls.css'
// import { sendMsg } from '../api';
import Tracklist from './Tracklist'
import EntityControl from './EntityControl';
import round from '../../../Utils';
// import {w3cwebsocket as W3CWebSocket} from "websocket"
import Tab from 'react-bootstrap/Tab';
import Tabs from 'react-bootstrap/Tabs';
import Track from '../../Track';
class Controls extends React.Component
{
state = {
EntityOnFocus: undefined,
}
getEntityOnEdit(Entities)
{
// console.log(Entities.getTracks());
// Entities.getTracks().map()
var val = undefined;
var array = Entities.getTracks();
for (let index = 0; index < array.length; index++)
{
if(array[index].isOnEdit === true)
{
val = array[index];
}
}
return val;
}
saveScenario(e)
{
var msg =
{
Data: "Scenario",
Type: "Save",
}
this.props.Client.send(JSON.stringify(msg))
console.log("save scenario")
}
render() {
// console.log(getEntityFromID(this.props.Entities,this.props.EntityOnFocus));
return (
<div className="controls">
<Tracklist entities= {this.props.TrackList} />
<br />
<div><button onClick={ this.saveScenario}> Save Scenario</button></div>
{/* <Tabs
defaultActiveKey="home"
transition={false}
id="noanim-tab-example"
className="mb-3"
>
<Tab eventKey="home" title="Home">
<EntityControl
Functions = {this.props.Functions}
Client = { this.props.Client }
TrackList = { this.props.TrackList}
PositionCliecked = {this.props.PositionClicked}
/>
</Tab>
<Tab eventKey="internalTrack" title="Internaltrack">
Internaltracklist
</Tab>
<Tab eventKey="orders" title="Orders">
Orders
</Tab>
</Tabs> */}
<div className="controls">
{/* <button onClick={this.props.updateEntities}>update</button> */}
</div>
</div>
);
}
}
export default Controls;

View File

@@ -0,0 +1,3 @@
import EntityControl from "../EntityControl/EntityControl";
export default EntityControl;

View File

@@ -0,0 +1,136 @@
import { useEffect } from "react";
import { useState } from "react";
import { useInterval } from 'usehooks-ts'
const config = {
// apiUrl: process.env.REACT_APP_WEBAPP_WS_URL,
apiUrl: "192.168.3.13",
apiProt: 30747
}
const client = new WebSocket("ws://"+config.apiUrl+":"+config.apiProt+"/");
function EntityTrackList( props)
{
const [Entity, setEntity] = useState(undefined);
// const [client, setclient] = useState(new WebSocket("ws://"+config.apiUrl+":"+config.apiProt+"/"));
// const client = new WebSocket("ws://"+config.apiUrl+":"+config.apiProt+"/");
const [TrackList, setTrackList] = useState([]);
client.onopen =() =>{
console.log("ws client for tracklist is connected");
// var msg =
// {
// Data: "COP",
// Type: "EntityTrackList"
// }
// client.send(JSON.stringify(msg));
}
client.onmessage = (message) => {
console.log(message);
if(Array.isArray(message)=== true)
{
}
}
useInterval(
() => {
// Your custom logic here
// setCount(count + 1)
console.log("updating")
updateTracklist();
},
// Delay in milliseconds or null to stop it
5000
// isPlaying ? delay : null,
)
const updateTracklist = () =>
{
if(Entity !== undefined)
{
var msg =
{
Data: "COP",
Type: "EntityTrackList",
ID: props.Entity.Id
}
client.send(JSON.stringify(msg));
}
}
useEffect(() => {
console.log("wird neu gerendert")
},[])
useEffect(() => {
if(props.Entity !== undefined)
{
if(Entity === undefined)
{
setEntity(props.Entity);
}else
{
if(props.Entity.Id !== Entity.Id)
{
setEntity(props.Entity);
}
}
updateTracklist();
}
console.log(props.Entity);
},[props.Entity])
// useEffect(() =>
// {
// },[Entity])
return(
<div className="tracklist">
<table>
<thead>
<tr className='TracklistHeader'>
<th className='TracklistHeaderCell'>Name</th>
<th className='TracklistHeaderCell'>Course</th>
<th className='TracklistHeaderCell'>Speed</th>
<th className='TracklistHeaderCell'>Distance</th>
<th className='TracklistHeaderCell'>Bearing</th>
<th className='TracklistHeaderCell'>Kind</th>
<th className='TracklistHeaderCell'>Side</th>
</tr>
</thead>
<tbody>
{ TrackList.map((val,index) => {
return (
<tr key={index}>
<td >{val.Name}</td>
<td className='TracklistCell'>{val.Course}</td>
<td className='TracklistCell'>{val.Speed}</td>
<td className='TracklistCell'>{}</td>
<td className='TracklistCell'>{}</td>
<td className='TracklistCell'>{val.Type}</td>
<td className='TracklistCell'>{val.Side}</td>
</tr>
)
})}
</tbody>
</table>
</div>
)}
export default EntityTrackList;

View File

@@ -0,0 +1,3 @@
import EntityTrackList from "./EntityTrackList";
export default EntityTrackList;

View File

@@ -0,0 +1,249 @@
// import { useMapEvents } from 'react-leaflet/hooks'
import React, { Component } from "react";
import { MapContainer, TileLayer,Marker, Popup, Tooltip,useMapEvents } from 'react-leaflet'
import { createIcon } from "./icon";
import "./OpenSeaMap.scss";
// import {w3cwebsocket as W3CWebSocket} from "websocket";
// import ContainerDimensions from 'react-container-dimensions';
import {} from "leaflet-contextmenu";
import Track from "../../Track.tsx";
class OpenSeaMap extends Component {
state = {
EntityOnFocus: Track,
}
updateDimensions() {
const height = window.innerWidth >= 950 ? window.innerHeight : 400
this.setState({ height: height })
}
componentWillMount() {
this.updateDimensions()
}
componentDidMount() {
window.addEventListener("resize", this.updateDimensions.bind(this))
// console.log(this.state.EntityOnFocus);
}
componentWillUnmount() {
window.removeEventListener("resize", this.updateDimensions.bind(this))
}
handleClick(e)
{
// console.log(e.target)
// this.setState({ currentPos: e.latlng });
// console.log(e.latlng);
// console.log("hello world");
}
markerOnClick(e) {
// console.log(e);
}
setOnEdit(e)
{
var Track = this.props.TrackList.getTrack(e.relatedTarget.options.data);
Track.isOnEdit = true;
// console.log(Track);
this.setState((state) =>({
EntityOnFocus: Track
}))
this.props.setEntityOnFocus(Track.Id);
}
deleteEntity(id)
{
// console.log(id);
try {
var msg =
{
Data: "Entity",
Type: "Delete",
ID: id,
}
this.props.TrackList.deteleTrack(id);
this.props.Client.send(JSON.stringify(msg))
this.forceUpdate();
} catch (error) {
console.log(error);
}
}
resetFocus(e)
{
if(e !== undefined)
{
var Track = this.props.TrackList.getTrack(e.relatedTarget.options.data);
if(Track !== undefined)
{
Track.isOnEdit = false;
}
console.log(Track);
}
this.props.setEntityOnFocus(undefined);
}
setNewEntityPosition(e)
{
// console.log(e.target.options);
var Track = this.props.TrackList.getTrack(e.target.options.data);
Track.isOnEdit = true;
Track.Position = [e.target._latlng.lat,e.target._latlng.lng];
// console.log(Track);
this.props.setEntityOnFocus(e.target.options.data);
}
makeIcon(index, entity,props,state)
{
// console.log(entity);
var isOnFocus = false;
if(entity.isOnEdit === true)
{
isOnFocus = true
}
var icon = createIcon(entity.Type,entity.Side)
return (
<Marker name={entity.Name} key={entity.Id} icon={icon} position={entity.Position} data={entity.Id}
draggable={isOnFocus? true : false}
contextmenu={true}
contextmenuWidth={140}
contextmenuItems={[{
text:"Edit",
index:0,
callback: this.setOnEdit.bind(this),
},{
text:"Delete",
index: 1,
callback: this.deleteEntity.bind(this,entity.Id),
}, {
text:"Test",
callback: this.markerOnClick.bind(this),
// separator: true,
index: 2
}, {
text:"Exit",
callback: this.resetFocus.bind(this),
index: 3
}]}
eventHandlers={{
click: (e) => {
console.log(entity.Name);
console.log(entity.id);
this.props.setEntityOnFocus(e.target.options.data)
},
dragend: this.setNewEntityPosition.bind(this),
}}
// eventHandlers={this.MarkerEventHandler}
>
<Tooltip direction='bottom' offset={[0,13]} opacity={1} permanent>
<span>{entity.Name}</span>
</Tooltip>
</Marker>
);
}
setPos(props)
{
console.log(props);
this.props.setFocusPosition([1,2])
}
render() {
const LocationFinderDummy = (props) => {
const map = useMapEvents({
click(e) {
// console.log(props)
props.props.resetFocus();
// props.props.state.EntityOnFocus =undefined
// props.props.props.setEntityOnFocus(undefined);
props.props.props.setFocusPosition(e.latlng)
// console.log(e.latlng);
},
});
// this.state.EntityOnFocus = undefined;
return null;
};
return (
<div className='map' style={{ height: this.state.height }} >
<MapContainer center={[54, 11]} zoom={6} scrollWheelZoom={true} contextmenu={false} attributionControl={false}
>
<LocationFinderDummy props ={{props:this.props, resetFocus:this.resetFocus.bind(this)}} />
<TileLayer
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" />
<TileLayer
url="https://tiles.openseamap.org/seamark/{z}/{x}/{y}.png" />
{
this.props.TrackListMap.map((element,index) => (
this.makeIcon(index,element)
))}
</MapContainer>
</div>
);
}
}
export default OpenSeaMap;

View File

@@ -93,4 +93,11 @@ const Hostile = new Icon({
iconAnchor: [hostile.getAnchor().x, hostile.getAnchor().y],
});
export { iconShip, friend,Hostile,createIcon };
var OwnShipIcon = new Icon({
iconUrl: 'Own_Ship.svg',
iconSize: [50, 50], // size of the icon
});
export {OwnShipIcon, iconShip, friend,Hostile,createIcon };

View File

@@ -1,13 +1,23 @@
import React from 'react';
import './tracklist.scss'
import round from '../../Utils';
import round from '../../../Utils';
import {useState,useEffect, useRef} from 'react';
class Tracklist extends React.Component
{
render()
{
// class Tracklist extends React.Component
function Tracklist(props) {
useEffect(() => {
console.log("tracklist")
},[])
return(
<div className="tracklist">
@@ -25,14 +35,14 @@ class Tracklist extends React.Component
</tr>
</thead>
<tbody>
{ this.props.entities.map((val,index) => {
{ props.entities.getTracks().map((val,index) => {
return (
<tr key={index}>
<td >{val.Name}</td>
<td className='TracklistCell'>{val.Course}</td>
<td className='TracklistCell'>{val.Speed}</td>
<td className='TracklistCell'>{round(val.Position[0],3)}</td>
<td className='TracklistCell'>{round(val.Position[1],3)}</td>
<td className='TracklistCell'>{round(val.Position[0],4)}</td>
<td className='TracklistCell'>{round(val.Position[1],4)}</td>
<td className='TracklistCell'>{val.Type}</td>
<td className='TracklistCell'>{val.Side}</td>
@@ -43,7 +53,7 @@ class Tracklist extends React.Component
</table>
</div>
);
}
}

View File

@@ -2,8 +2,9 @@
{
display: flex;
max-height: 40%;
height: 300px;
height: 400px;
display: block;
overflow: scroll;
}
td

View File

@@ -0,0 +1,3 @@
import SimControl from "./SimControl.jsx";
export default SimControl;

View File

@@ -0,0 +1,133 @@
export function getCOP()
{
var msg =
{
Type: "Request",
Data: "COP"
}
return JSON.stringify(msg);
}
export function deleteEntity(id)
{
var msg =
{
Data: "Entity",
Type: "Delete",
ID: id,
}
return JSON.stringify(msg);
}
export function createEntity(Name,Speed,Course, Position, Height)
{
var msg =
{
Data: "Entity",
Type: "New",
Name: Name,
Speed: Speed.toString(),
Course: Course.toString(),
Position: [Position[0].toString(),Position[1].toString()],
Height: Height.toString(),
SetPosition: true
}
return JSON.stringify(msg);
}
export function updateEntity(id,Speed,Course, Position, Height, setPos)
{
var msg =
{
Data: "Entity",
Type: "Update",
ID: id,
Speed: Speed.toString(),
Course: Course.toString(),
Position: [Position[0].toString(),Position[1].toString()],
Height: Height.toString(),
SetPosition: setPos
}
return JSON.stringify(msg);
}
export function getTracklist(id)
{
var msg =
{
Data: "COP",
Type: "EntityTrackList",
ID: id,
}
return JSON.stringify(msg);
}
export function saveScenario()
{
var msg =
{
Data: "Scenario",
Type: "Save",
}
return JSON.stringify(msg);
}
export function deleteScenario()
{
var msg =
{
Data: "Scenario",
Type: "Delete",
}
return JSON.stringify(msg);
}
export function loadScenario(id)
{
var msg =
{
Data: "Scenario",
Type: "Load",
ID: id,
}
return JSON.stringify(msg);
}
export function loadAutoraster()
{
var msg =
{
Data: "Scenario",
Type: "AutoRaster",
}
return JSON.stringify(msg);
}
export function getWSTestMessage()
{
var msg =
{
Data: "TEST",
}
return JSON.stringify(msg);
}

View File

@@ -1,30 +0,0 @@
// // api/index.js
// var socket = new WebSocket("ws://localhost:8008/");
// let connect = cb => {
// console.log("connecting");
// socket.onopen = () => {
// console.log("Successfully Connected");
// };
// socket.onmessage = msg => {
// cb(msg);
// };
// socket.onclose = event => {
// console.log("Socket Closed Connection: ", event);
// };
// socket.onerror = error => {
// console.log("Socket Error: ", error);
// };
// };
// let sendMsg = msg => {
// console.log("sending msg: ", msg);
// socket.send(msg);
// };
// export { connect, sendMsg };

View File

@@ -1,111 +0,0 @@
import React from 'react';
import './controls.css'
// import { sendMsg } from '../api';
import Tracklist from './Tracklist'
import EntityControl from './EntityControl';
import round from '../Utils';
// import {w3cwebsocket as W3CWebSocket} from "websocket"
// const config = {
// // apiUrl: process.env.REACT_APP_WEBAPP_WS_URL,
// apiUrl: "192.168.3.13",
// apiProt: 30747
// }
// const client = new W3CWebSocket("ws://"+config.apiUrl+":"+config.apiProt+"/");
// const round = (n, dp) => {
// const h = +('1'.padEnd(dp + 1, '0')) // 10 or 100 or 1000 or etc
// return Math.round(n * h) / h
// }
class Controls extends React.Component
{
state = {
EntityOnFocus: undefined,
}
componentDidMount()
{
// client.onopen = () => {
// console.log(" Control Websocket Client Connected");
// };
// client.onmessage = (message) => {
// const dataFromServer = JSON.parse(message.data);
// console.log('reply', dataFromServer);
// }
}
render() {
function getEntityFromID(Entities, SelectedEntity)
{
if(SelectedEntity !== undefined)
{
var tmp = {};
// tmp.NewPos= new Array(0,0);
// console.log(tmp);
Entities.map((val) => {
if(val.id === SelectedEntity.EntityID)
{
tmp = val
// tmp = JSON.parse(JSON.stringify(val));
// console.log(tmp);
val.dragged = SelectedEntity.dragged;
if(SelectedEntity.dragged === undefined)
{
val.dragged = false;
}
// console.log(SelectedEntity);
// if(SelectedEntity.NewPos !== undefined)
// {
// // Object.assign(tmp, {NewPos: [{},{}]});
// Object.assign(val, {NewPos: [{},{}]});
// // tmp.OldPos = val.Position;
// // Object.assign(tmp, {NewPos: [round(SelectedEntity.NewPos[0],3),0]});
// // val.NewPos = SelectedEntity.NewPos;
// val.NewPos[0] = round(SelectedEntity.NewPos[0],3);
// val.NewPos[1] = round(SelectedEntity.NewPos[1],3);
// // tmp.NewPos[0] = round(SelectedEntity.NewPos[0],3);
// // tmp.NewPos[1] = round(SelectedEntity.NewPos[1],3);
// }
// tmp.NewPos = SelectedEntity.NewPos;
}
return tmp;
})
// console.log(tmp);
return tmp;
}
}
// console.log(getEntityFromID(this.props.Entities,this.props.EntityOnFocus));
return (
<div className="controls">
<Tracklist entities= {this.props.Entities} />
<br />
<div>
{/* <button onClick={this.props.updateEntities}>update</button> */}
<EntityControl Functions = {this.props.Functions} Client = { this.props.Client } Entity = { getEntityFromID(this.props.Entities,this.props.EntityOnFocus)} EntityOnFocus= {this.props.EntityOnFocus} PositionCliecked = {this.props.PositionClicked}/>
</div>
</div>
);
}
}
export default Controls;

View File

@@ -1,3 +0,0 @@
import Controls from "./controls.jsx";
export default Controls;