Session 022: Migrate Lts/Tools (72 submodules) to Qt6
- Copied complete Lts/Tools directory from qt5-before to qt6-after - Updated 32 CMakeLists.txt files (Qt5:: → Qt6::) - Tools subdirectory already included in parent Lts/CMakeLists.txt - All 72 tool submodules now present in qt6-migrated-legatus - Includes: Adler, ArAssets, ConfigX, DBAdministration, DBUpdate, DdsView, InfrastructureManagement, and 67 more tools - C++17 standard (RTI 7.7.0 compatibility maintained) - Tests deferred until final validation phase Migration progress: ✅ Lts/Application (68 files) ✅ Lts/DDS (verified Qt6-ready) ✅ Lts/Plugins (60 submodules, verified) ✅ Lts/Projects (5 submodules, verified) ✅ Lts/Proxies (13 submodules, verified) ✅ Lts/Services (47 submodules, verified) ✅ Lts/Tools (72 submodules, copied + CMake updated) ✅ Lts/Viewer (LtsFileDialog, LtsReportDisplay, SituationMap) 🟡 TetraIF (partial) 🟡 Z (partial: nglwidgets, QtSingleApplication, qsql_informix, WMS_Interface)
This commit is contained in:
1 parent
01dce5fe52
commit
d5c13033e2
1792 files changed
+982018
No files matched your search
@@ -0,0 +1,38 @@
|
||||
.App {
|
||||
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);
|
||||
}
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
import React from 'react';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import App from './App';
|
||||
|
||||
test('renders learn react link', () => {
|
||||
render(<App />);
|
||||
const linkElement = screen.getByText(/learn react/i);
|
||||
expect(linkElement).toBeInTheDocument();
|
||||
});
|
||||
@@ -0,0 +1,24 @@
|
||||
import { BrowserRouter as Router, Route, Routes, Navigate } from 'react-router-dom';
|
||||
|
||||
import Dashboard from './pages/dashboard/Dashboard';
|
||||
import Login from './pages/loginpage/Login';
|
||||
import Replay from './pages/replaypage/Replay';
|
||||
import ProtectedRoute from './components/ProtectedRoute';
|
||||
import PublicRoute from './components/PublicRoute';
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<Router>
|
||||
<div className="App">
|
||||
<Routes>
|
||||
<Route path="/login" element={<PublicRoute element={<Login />} />} />
|
||||
<Route path="/dashboard" element={<ProtectedRoute element={<Dashboard />} />} />
|
||||
<Route path="/" element={<Navigate to="/login" />} />
|
||||
<Route path="/replays/replay" element={<ProtectedRoute element={<Replay />} />} />
|
||||
</Routes>
|
||||
</div>
|
||||
</Router>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
import axios from "axios"
|
||||
|
||||
export const LoginUser = async (username: string, password: string): Promise<boolean> => {
|
||||
try {
|
||||
const response = await axios.post("http://localhost:8080/login", {
|
||||
username: username,
|
||||
password: password,
|
||||
}, {
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
document.cookie = `token=${response.data.token}; path=/; Secure; SameSite=Strict`;
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error("Error:", error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export const isAuthenticated = async (): Promise<boolean> => {
|
||||
const token = document.cookie.split('; ').find(row => row.startsWith('token='));
|
||||
|
||||
if (!token) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
const response = await axios.get('http://localhost:8080/auth', {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token.split('=')[1]}`,
|
||||
},
|
||||
});
|
||||
return response.status === 200;
|
||||
} catch (error) {
|
||||
console.error('Authentication check failed:', error);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
import axios from "axios";
|
||||
import { Marker } from "../types/marker";
|
||||
|
||||
export const getBookmarks = async (replayId: string) => {
|
||||
try {
|
||||
const response = await axios.get<Marker[]>('http://localhost:8080/replays/replay/bookmark?replayId=' + replayId);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('Error fetching bookmarks:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
import { GeoJson } from "../types/geojson";
|
||||
import axios from 'axios';
|
||||
import { ReplayInitialInfo } from '../types/replay';
|
||||
|
||||
export const getGeoObjects = async (replayId: string) => {
|
||||
try {
|
||||
const response = await axios.get<GeoJson[]>('http://localhost:8080/objects/replay?replayId=' + replayId);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('Error fetching geoobjects:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export const getInitialReplayInformation = async (replayId: string) => {
|
||||
try {
|
||||
const response = await axios.get<ReplayInitialInfo>('http://localhost:8080/objects/replayinit?replayId=' + replayId);
|
||||
console.log('Initial replay information:', response.data);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('Error fetching initial replay information:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export const getGeoObjectsInterval = async (replayId: string, min_timestamp: number, max_timestamp : number) => {
|
||||
try {
|
||||
const response = await axios.get<GeoJson[]>('http://localhost:8080/objects/replay?replayId=' + replayId + '&start=' + min_timestamp + '&end=' + max_timestamp);
|
||||
return response.data;
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error fetching geoobjects:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
import axios from 'axios';
|
||||
import { Replay } from '../types/replay';
|
||||
|
||||
|
||||
export const getReplays = async (): Promise<Replay[]> => {
|
||||
try {
|
||||
const token = document.cookie
|
||||
.split('; ')
|
||||
.find(row => row.startsWith('token='))
|
||||
?.split('=')[1];
|
||||
const response = await axios.get<Replay[]>('http://localhost:8080/replays/user?token=' + token);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('Error fetching replays:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const deleteReplay = async (uuid: string): Promise<void> => {
|
||||
try {
|
||||
await axios.delete(`http://localhost:8080/replays/delete?replayId=${uuid}`);
|
||||
} catch (error) {
|
||||
console.error('Error deleting replay:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export const getAvgLatLng = async (replayId: string): Promise<{ lat: number, lng: number }> => {
|
||||
try {
|
||||
const response = await axios.get<{ lat: number, lng: number }>(`http://localhost:8080/replays/replay/avg?replayId=${replayId}`);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('Error fetching average LatLng:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
+131
@@ -0,0 +1,131 @@
|
||||
import { useEffect, useMemo, useState, FC } from 'react';
|
||||
import { MapContainer, TileLayer, Marker, Polyline, Polygon, Popup } from 'react-leaflet';
|
||||
import L from 'leaflet';
|
||||
|
||||
import icon from 'leaflet/dist/images/marker-icon.png';
|
||||
import iconShadow from 'leaflet/dist/images/marker-shadow.png';
|
||||
import { GeoJson } from '../types/geojson';
|
||||
|
||||
import 'leaflet/dist/leaflet.css';
|
||||
import '../styles/map.css';
|
||||
import { getAvgLatLng } from '../api/replay';
|
||||
import { getIcon } from '../utils/markerSymbol';
|
||||
|
||||
// Standard-Icon für Leaflet
|
||||
const DefaultIcon = L.icon({
|
||||
iconUrl: icon,
|
||||
shadowUrl: iconShadow,
|
||||
iconSize: [25, 41],
|
||||
iconAnchor: [12.5, 41],
|
||||
});
|
||||
L.Marker.prototype.options.icon = DefaultIcon;
|
||||
|
||||
interface ReplayIdProps {
|
||||
geoJsons: GeoJson[];
|
||||
replayId: string;
|
||||
}
|
||||
|
||||
const StaticMap: FC<ReplayIdProps> = ({ geoJsons, replayId }) => {
|
||||
const [avgLatLng, setAvgLatLng] = useState<L.LatLng | null>(null);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const iconCache = useMemo(() => new Map<string, L.Icon>(), []);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchAvgLatLng = async () => {
|
||||
try {
|
||||
const avgLatLngResult = await getAvgLatLng(replayId);
|
||||
if (Array.isArray(avgLatLngResult) && avgLatLngResult.length > 0 && typeof avgLatLngResult[0].lat === 'number' && typeof avgLatLngResult[0].lng === 'number') {
|
||||
setAvgLatLng(new L.LatLng(avgLatLngResult[0].lat, avgLatLngResult[0].lng));
|
||||
} else {
|
||||
throw new Error('Invalid LatLng object');
|
||||
}
|
||||
setIsLoading(false);
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Abrufen von avgLatLng:', error);
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchAvgLatLng();
|
||||
}, [replayId]);
|
||||
|
||||
const tileLayer = useMemo(
|
||||
() => <TileLayer url="https://tile.openstreetmap.de/{z}/{x}/{y}.png" />,
|
||||
[]
|
||||
);
|
||||
|
||||
const icons = useMemo(() => {
|
||||
return geoJsons.map((marker) => {
|
||||
const { id, sidic } = marker.properties;
|
||||
const key = `${id}-${sidic}`;
|
||||
|
||||
// Prüfe, ob das Icon im Cache vorhanden ist
|
||||
if (!iconCache.has(key)) {
|
||||
iconCache.set(key, getIcon(marker));
|
||||
}
|
||||
|
||||
return iconCache.get(key)!; // Das "!" ist hier sicher, da wir garantiert vorher das Icon hinzufügen
|
||||
});
|
||||
}, [geoJsons, iconCache]);
|
||||
|
||||
const renderGeoJson = useMemo(() => {
|
||||
return geoJsons.map((marker, index) => {
|
||||
|
||||
const renderProperties = () => {
|
||||
return Object.entries(marker.properties).map(([key, value]) => (
|
||||
<p key={key}><strong>{key}:</strong> {value}</p>
|
||||
));
|
||||
};
|
||||
|
||||
|
||||
switch (marker.geometry.type) {
|
||||
case 'Point':
|
||||
return (
|
||||
<Marker
|
||||
key={index}
|
||||
position={marker.geometry.coordinates as L.LatLngExpression}
|
||||
icon={icons[index]}
|
||||
>
|
||||
<Popup>
|
||||
{renderProperties()}
|
||||
</Popup>
|
||||
</Marker>
|
||||
);
|
||||
case 'LineString':
|
||||
return (
|
||||
<Polyline
|
||||
key={index}
|
||||
positions={marker.geometry.coordinates as L.LatLngExpression[]}
|
||||
/>
|
||||
);
|
||||
case 'Polygon':
|
||||
return (
|
||||
<Polygon
|
||||
key={index}
|
||||
positions={marker.geometry.coordinates as L.LatLngExpression[][]}
|
||||
/>
|
||||
);
|
||||
default:
|
||||
console.warn(`Unsupported geometry type: ${marker.geometry.type}`);
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}, [geoJsons, icons]);
|
||||
|
||||
if (isLoading || !avgLatLng) {
|
||||
return (
|
||||
<div className="flex h-80vh items-center justify-center">
|
||||
Loading...
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<MapContainer center={avgLatLng} zoom={13}>
|
||||
{tileLayer}
|
||||
{renderGeoJson}
|
||||
</MapContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export default StaticMap;
|
||||
Executable
+23
@@ -0,0 +1,23 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Navigate } from 'react-router-dom';
|
||||
import { isAuthenticated } from '../api/auth';
|
||||
|
||||
const ProtectedRoute = ({ element }: { element: JSX.Element }) => {
|
||||
const [auth, setAuth] = useState<boolean | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const checkAuth = async () => {
|
||||
const authStatus = await isAuthenticated();
|
||||
setAuth(authStatus);
|
||||
};
|
||||
checkAuth();
|
||||
}, []);
|
||||
|
||||
if (auth === null) {
|
||||
return <div>Loading...</div>;
|
||||
}
|
||||
|
||||
return auth ? element : <Navigate to="/login" />;
|
||||
};
|
||||
|
||||
export default ProtectedRoute;
|
||||
Executable
+23
@@ -0,0 +1,23 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Navigate } from 'react-router-dom';
|
||||
import { isAuthenticated } from '../api/auth';
|
||||
|
||||
const PublicRoute = ({ element }: { element: JSX.Element }) => {
|
||||
const [auth, setAuth] = useState<boolean | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const checkAuth = async () => {
|
||||
const authStatus = await isAuthenticated();
|
||||
setAuth(authStatus);
|
||||
};
|
||||
checkAuth();
|
||||
}, []);
|
||||
|
||||
if (auth === null) {
|
||||
return <div>Loading...</div>; // Oder ein anderes Lade-Element
|
||||
}
|
||||
|
||||
return auth ? <Navigate to="/dashboard" /> : element;
|
||||
};
|
||||
|
||||
export default PublicRoute;
|
||||
+156
@@ -0,0 +1,156 @@
|
||||
import React, { useState, useMemo, useCallback, useRef, useEffect } from 'react';
|
||||
import { GeoJson } from '../types/geojson';
|
||||
import debounce from 'lodash.debounce';
|
||||
|
||||
|
||||
interface CustomSliderProps {
|
||||
geoJsons: GeoJson[];
|
||||
setDisplayGeoJsons: React.Dispatch<React.SetStateAction<GeoJson[]>>;
|
||||
setOpenPopup: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
markerValue: number;
|
||||
minTimestamp: number;
|
||||
maxTimestamp: number;
|
||||
replayId: string;
|
||||
}
|
||||
|
||||
const CustomSlider: React.FC<CustomSliderProps> = ({
|
||||
geoJsons,
|
||||
setDisplayGeoJsons,
|
||||
setOpenPopup,
|
||||
markerValue,
|
||||
minTimestamp,
|
||||
maxTimestamp
|
||||
}) => {
|
||||
// State variables
|
||||
const [currentTimestamp, setCurrentTimestamp] = useState<number>(minTimestamp);
|
||||
const [isPlaying, setIsPlaying] = useState(false);
|
||||
const [playbackSpeed, setPlaybackSpeed] = useState(1);
|
||||
const [filterParty, setFilterParty] = useState<string | null>(null);
|
||||
|
||||
const intervalRef = useRef<NodeJS.Timeout | null>(null);
|
||||
|
||||
// Worker Reference for background processing
|
||||
const workerRef = useRef<Worker | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
workerRef.current = new Worker(new URL('../utils/workers.ts', import.meta.url));
|
||||
return () => workerRef.current?.terminate();
|
||||
}, []);
|
||||
|
||||
// Debounced update function
|
||||
const debouncedUpdateGeoJsons = useMemo(() => {
|
||||
return debounce((timestamp: number) => {
|
||||
if (!workerRef.current) return;
|
||||
workerRef.current.postMessage({ geoJsons, timestamp, filterParty });
|
||||
}, 200);
|
||||
}, [geoJsons, filterParty]);
|
||||
|
||||
// Listen for worker messages
|
||||
useEffect(() => {
|
||||
if (!workerRef.current) return;
|
||||
|
||||
workerRef.current.onmessage = (e) => {
|
||||
setDisplayGeoJsons(e.data);
|
||||
};
|
||||
}, [setDisplayGeoJsons]);
|
||||
|
||||
// Handle timestamp and update GeoJSONs
|
||||
const handleSliderChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const timestamp = Number(event.target.value);
|
||||
setCurrentTimestamp(timestamp);
|
||||
debouncedUpdateGeoJsons(timestamp);
|
||||
};
|
||||
|
||||
// Toggle play/pause functionality
|
||||
const togglePlayback = () => {
|
||||
if (isPlaying) {
|
||||
clearInterval(intervalRef.current!);
|
||||
} else {
|
||||
intervalRef.current = setInterval(() => {
|
||||
setCurrentTimestamp((prev) => {
|
||||
const nextTimestamp = prev + 1000 * playbackSpeed;
|
||||
if (nextTimestamp >= maxTimestamp) {
|
||||
clearInterval(intervalRef.current!);
|
||||
setIsPlaying(false);
|
||||
return maxTimestamp;
|
||||
}
|
||||
debouncedUpdateGeoJsons(nextTimestamp);
|
||||
return nextTimestamp;
|
||||
});
|
||||
}, 1000);
|
||||
}
|
||||
setIsPlaying((prev) => !prev);
|
||||
};
|
||||
|
||||
// Toggle playback speed
|
||||
const toggleSpeed = () => setPlaybackSpeed((prev) => (prev === 1 ? 2 : 1));
|
||||
|
||||
// Handle filter change
|
||||
const handleFilterChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
|
||||
setFilterParty(event.target.value || null);
|
||||
};
|
||||
|
||||
// Return early if no data
|
||||
if (!geoJsons.length) return <div>Loading...</div>;
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-2 p-4 bg-white shadow-md rounded-lg">
|
||||
{/* Slider */}
|
||||
<div>
|
||||
<input
|
||||
type="range"
|
||||
value={currentTimestamp}
|
||||
onChange={handleSliderChange}
|
||||
min={minTimestamp}
|
||||
max={maxTimestamp}
|
||||
step={1000}
|
||||
className="w-full h-2 appearance-none rounded-lg bg-gray-200 outline-none cursor-pointer transition-all focus:ring-2 focus:ring-blue-500"
|
||||
style={{
|
||||
background: `linear-gradient(to right, #3182ce ${((currentTimestamp - minTimestamp) / (maxTimestamp - minTimestamp)) * 100}%, #e5e7eb ${((currentTimestamp - minTimestamp) / (maxTimestamp - minTimestamp)) * 100}%)`,
|
||||
}}
|
||||
/>
|
||||
<div className="mt-2 text-center text-gray-700 text-lg font-medium">
|
||||
{new Date(currentTimestamp).toLocaleString()}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Playback Controls */}
|
||||
<div className="flex justify-center gap-4">
|
||||
<button
|
||||
onClick={togglePlayback}
|
||||
className="px-6 py-2 bg-blue-500 text-white font-semibold rounded-lg shadow hover:bg-blue-600 transition-all"
|
||||
>
|
||||
{isPlaying ? 'Pause' : 'Play'}
|
||||
</button>
|
||||
<button
|
||||
onClick={toggleSpeed}
|
||||
className="px-6 py-2 bg-green-500 text-white font-semibold rounded-lg shadow hover:bg-green-600 transition-all"
|
||||
>
|
||||
{playbackSpeed === 1 ? 'Speed Up' : 'Slow Down'}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setOpenPopup(true)}
|
||||
className="px-6 py-2 bg-red-500 text-white font-semibold rounded-lg shadow hover:bg-red-600 transition-all"
|
||||
>
|
||||
Open Dialogue
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Filter Dropdown */}
|
||||
<div className="flex items-center gap-4">
|
||||
<label className="text-gray-700 font-medium">Filter by Party:</label>
|
||||
<select
|
||||
value={filterParty || ''}
|
||||
onChange={handleFilterChange}
|
||||
className="px-4 py-2 bg-gray-100 border border-gray-300 rounded-lg shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
>
|
||||
<option value="">No Filter</option>
|
||||
<option value="Blue">Blue</option>
|
||||
<option value="Red">Red</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CustomSlider;
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
interface ActionButtonProps {
|
||||
color: 'blue' | 'red' | 'yellow';
|
||||
onClick: () => void;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export const ActionButton: React.FC<ActionButtonProps> = ({ color, onClick, children }) => {
|
||||
const colors = {
|
||||
blue: 'bg-blue-500 hover:bg-blue-700',
|
||||
red: 'bg-red-500 hover:bg-red-700',
|
||||
yellow: 'bg-yellow-500 hover:bg-yellow-700',
|
||||
};
|
||||
|
||||
return (
|
||||
<button
|
||||
onClick={onClick}
|
||||
className={`${colors[color]} text-white font-medium py-2 px-4 rounded transition-all`}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
interface TableHeaderProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
const TableHeader: React.FC<TableHeaderProps> = ({ children }) => (
|
||||
<th className="py-3 px-4 text-left font-medium">{children}</th>
|
||||
);
|
||||
|
||||
export default TableHeader;
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
import { useState } from "react";
|
||||
import { Replay } from "../../types/replay";
|
||||
import { ActionButton } from "./ActionButton";
|
||||
|
||||
interface ReplayRowProps {
|
||||
replay: Replay;
|
||||
onView: (uuid: string) => void;
|
||||
onDelete: (uuid: string) => void;
|
||||
}
|
||||
|
||||
const ReplayRow: React.FC<ReplayRowProps> = ({ replay, onView, onDelete }) => {
|
||||
const [isDialogOpen, setIsDialogOpen] = useState(false);
|
||||
|
||||
const openDialog = () => setIsDialogOpen(true);
|
||||
const closeDialog = () => setIsDialogOpen(false);
|
||||
|
||||
return (
|
||||
<>
|
||||
<tr className="border-b hover:bg-gray-100">
|
||||
<td className="py-3 px-4">{replay.name}</td>
|
||||
<td className="py-3 px-4">{replay.uuid}</td>
|
||||
<td className="py-3 px-4">{replay.created_by}</td>
|
||||
<td className="py-3 px-4">{new Date(replay.time_created * 1000).toLocaleString()}</td>
|
||||
<td className="py-3 px-4 flex gap-2">
|
||||
<ActionButton color="blue" onClick={() => onView(replay.uuid)}>
|
||||
View
|
||||
</ActionButton>
|
||||
<ActionButton color="red" onClick={() => onDelete(replay.uuid)}>
|
||||
Delete
|
||||
</ActionButton>
|
||||
<ActionButton color="yellow" onClick={() => openDialog()}>
|
||||
Details
|
||||
</ActionButton>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
{isDialogOpen && (
|
||||
<div className="fixed inset-0 flex items-center justify-center z-50 bg-gray-900 bg-opacity-50">
|
||||
<div className="bg-white rounded-lg shadow-lg p-8 max-w-md w-full z-10">
|
||||
<h2 className="text-2xl font-semibold text-gray-800 mb-4">Replay Details</h2>
|
||||
<div className="space-y-2 text-gray-700">
|
||||
<p><strong className="font-medium">Name:</strong> {replay.name}</p>
|
||||
<p><strong className="font-medium">UUID:</strong> {replay.uuid}</p>
|
||||
<p><strong className="font-medium">Created By:</strong> {replay.created_by}</p>
|
||||
<p><strong className="font-medium">Time Created:</strong> {new Date(replay.time_created * 1000).toLocaleString()}</p>
|
||||
</div>
|
||||
<div className="mt-6 flex justify-end">
|
||||
<ActionButton color="red" onClick={closeDialog}>
|
||||
Close
|
||||
</ActionButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
export default ReplayRow;
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
import { Replay } from "../../types/replay";
|
||||
import TableHeader from "./ReplayHeader";
|
||||
import ReplayRow from "./ReplayRow";
|
||||
|
||||
interface ReplayTableProps {
|
||||
replays: Replay[];
|
||||
onView: (uuid: string) => void;
|
||||
onDelete: (uuid: string) => void;
|
||||
}
|
||||
|
||||
const ReplayTable: React.FC<ReplayTableProps> = ({ replays, onView, onDelete }) => {
|
||||
return (
|
||||
<table className="min-w-full bg-white">
|
||||
<thead className="bg-gray-200 text-gray-700">
|
||||
<tr>
|
||||
<TableHeader>NAME</TableHeader>
|
||||
<TableHeader>UUID</TableHeader>
|
||||
<TableHeader>CREATED BY </TableHeader>
|
||||
<TableHeader>TIME CREATED</TableHeader>
|
||||
<TableHeader>ACTIONS</TableHeader>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{replays.map((replay) => (
|
||||
<ReplayRow
|
||||
key={replay.uuid}
|
||||
replay={replay}
|
||||
onView={onView}
|
||||
onDelete={onDelete}
|
||||
/>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
};
|
||||
|
||||
export default ReplayTable;
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import reportWebVitals from './reportWebVitals';
|
||||
|
||||
import { configureStore } from '@reduxjs/toolkit';
|
||||
import { Provider } from 'react-redux';
|
||||
|
||||
import App from './App';
|
||||
|
||||
import userReducer from './store/UserSlice';
|
||||
import mapReducer from './store/MapSlice';
|
||||
import './index.css';
|
||||
|
||||
const root = ReactDOM.createRoot(
|
||||
document.getElementById('root') as HTMLElement
|
||||
);
|
||||
|
||||
const store = configureStore (
|
||||
{
|
||||
reducer: {
|
||||
user: userReducer,
|
||||
map: mapReducer,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
root.render(
|
||||
<Provider store={store}>
|
||||
<App />
|
||||
</Provider>
|
||||
);
|
||||
|
||||
// If you want to start measuring performance in your app, pass a function
|
||||
// to log results (for example: reportWebVitals(console.log))
|
||||
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
|
||||
reportWebVitals();
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3"><g fill="#61DAFB"><path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/><circle cx="420.9" cy="296.5" r="45.7"/><path d="M520.5 78.1z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 2.6 KiB |
Executable
+53
@@ -0,0 +1,53 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { deleteReplay, getAvgLatLng, getReplays } from "../../api/replay";
|
||||
import { setAvgLatLng } from "../../store/MapSlice";
|
||||
import ReplayTable from "../../components/dashboard/ReplayTable";
|
||||
import { Replay } from "../../types/replay";
|
||||
|
||||
const Dashboard: React.FC = () => {
|
||||
const [replays, setReplays] = useState<Replay[]>([]);
|
||||
const dispatch = useDispatch();
|
||||
const navigate = useNavigate();
|
||||
|
||||
useEffect(() => {
|
||||
const fetchReplays = async () => {
|
||||
const data = await getReplays();
|
||||
setReplays(data);
|
||||
};
|
||||
fetchReplays();
|
||||
}, []);
|
||||
|
||||
const handleViewReplay = async (uuid: string) => {
|
||||
const avgLatLng = await getAvgLatLng(uuid);
|
||||
dispatch(setAvgLatLng(avgLatLng));
|
||||
navigate(`/replays/replay?replayId=${uuid}`);
|
||||
};
|
||||
|
||||
const handleDeleteReplay = async (uuid: string) => {
|
||||
await deleteReplay(uuid);
|
||||
const updatedReplays = await getReplays();
|
||||
setReplays(updatedReplays);
|
||||
};
|
||||
|
||||
return (
|
||||
<main className="container mx-auto p-6">
|
||||
<header className="mb-6">
|
||||
<h1 className="text-3xl font-bold text-gray-800">Replay Dashboard</h1>
|
||||
<p className="text-gray-600">Manage your Replays</p>
|
||||
<p className="text-gray-600"></p>
|
||||
</header>
|
||||
|
||||
<section className="overflow-x-auto bg-white shadow-md rounded-lg">
|
||||
<ReplayTable
|
||||
replays={replays}
|
||||
onView={handleViewReplay}
|
||||
onDelete={handleDeleteReplay}
|
||||
/>
|
||||
</section>
|
||||
</main>
|
||||
);
|
||||
};
|
||||
|
||||
export default Dashboard;
|
||||
Executable
+54
@@ -0,0 +1,54 @@
|
||||
// src/pages/loginpage/Login.tsx
|
||||
import React, { useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { LoginUser } from '../../api/auth';
|
||||
|
||||
const Login = () => {
|
||||
const [username, setUsername] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
const handleLogin = () => {
|
||||
LoginUser(username, password).then((success) => {
|
||||
if (success) {
|
||||
navigate('/dashboard');
|
||||
} else {
|
||||
alert('Login failed');
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-center min-h-screen bg-gray-100">
|
||||
<div className="w-full max-w-md p-8 space-y-6 bg-white rounded shadow-md">
|
||||
<h2 className="text-2xl font-bold text-center">Login</h2>
|
||||
<div className="space-y-4">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Username"
|
||||
value={username}
|
||||
onChange={(e) => setUsername(e.target.value)}
|
||||
className="w-full px-4 py-2 border rounded focus:outline-none focus:ring-2 focus:ring-blue-600"
|
||||
/>
|
||||
<input
|
||||
type="password"
|
||||
placeholder="Password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
className="w-full px-4 py-2 border rounded focus:outline-none focus:ring-2 focus:ring-blue-600"
|
||||
/>
|
||||
<button
|
||||
onClick={handleLogin}
|
||||
className="w-full px-4 py-2 font-bold text-white bg-blue-600 rounded hover:bg-blue-700"
|
||||
>
|
||||
Login
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Login;
|
||||
Executable
+100
@@ -0,0 +1,100 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
|
||||
import StaticMap from "../../components/Map";
|
||||
import CustomSlider from "../../components/Slider";
|
||||
|
||||
import { useSearchParams } from "react-router-dom";
|
||||
import { GeoJson } from "../../types/geojson";
|
||||
import { getInitialReplayInformation } from "../../api/geojson";
|
||||
import { Marker } from "../../types/marker";
|
||||
import { getBookmarks } from "../../api/bookmark";
|
||||
|
||||
const Replay: React.FC = () => {
|
||||
const [searchParams] = useSearchParams();
|
||||
const [geoJsons, setGeoJsons] = useState<GeoJson[]>([]);
|
||||
const [displayGeoJsons, setDisplayGeoJsons] = useState<GeoJson[]>([]);
|
||||
const [isLoading, setIsLoading] = useState<boolean>(true);
|
||||
const [openPopup, setOpenPopup] = useState<boolean>(false);
|
||||
const [markers, setMarkers] = useState<Marker[]>([]);
|
||||
const [sliderValue, setSliderValue] = useState<number>(0);
|
||||
const [minTimestamp, setMinTimestamp] = useState<number>(0);
|
||||
const [maxTimestamp, setMaxTimestamp] = useState<number>(0);
|
||||
|
||||
const replayId = searchParams.get("replayId");
|
||||
useEffect(() => {
|
||||
const fetchGeoObjects = async () => {
|
||||
try {
|
||||
const geoObjects = await getInitialReplayInformation(replayId || "");
|
||||
setGeoJsons(geoObjects.geojsons);
|
||||
setMinTimestamp(Number(geoObjects.timestamps.min_timestamp));
|
||||
setMaxTimestamp(Number(geoObjects.timestamps.max_timestamp));
|
||||
setIsLoading(false);
|
||||
} catch (error) {
|
||||
console.error("Error fetching geo objects:", error);
|
||||
} finally {
|
||||
}
|
||||
};
|
||||
fetchGeoObjects();
|
||||
}, [replayId]);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchBookmarks = async () => {
|
||||
try {
|
||||
const markers = await getBookmarks(replayId || "");
|
||||
setMarkers(markers);
|
||||
} catch (error) {
|
||||
console.error("Error fetching bookmarks:", error);
|
||||
}
|
||||
};
|
||||
fetchBookmarks();
|
||||
}, [replayId]);
|
||||
|
||||
return (
|
||||
<div className="h-[100%]">
|
||||
{isLoading ? (
|
||||
<div className="flex justify-center items-center h-full">
|
||||
Loading...
|
||||
</div>
|
||||
) : (
|
||||
<div>
|
||||
<StaticMap geoJsons={displayGeoJsons} replayId={replayId || ""} />
|
||||
<div className="w-full z-100 h-[20vh]">
|
||||
{openPopup && (
|
||||
<div className="absolute top-0 left-0 w-[30%] h-full bg-white bg-opacity-90 z-10">
|
||||
<div className="flex justify-between items-center p-2">
|
||||
<h2 className="text-lg font-bold">Marker Dialog</h2>
|
||||
<button onClick={() => setOpenPopup(false)}>Close</button>
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
{markers.map(marker => (
|
||||
<div key={marker.id} className="flex justify-between items-center p-2 border-b">
|
||||
<div>{marker.name}</div>
|
||||
<div>{new Date(marker.timestamp).toLocaleString()}</div>
|
||||
<button
|
||||
className="bg-blue-500 text-white rounded-lg px-2 py-1"
|
||||
onClick={() => setSliderValue(marker.timestamp)} // Update the slider value
|
||||
>
|
||||
Show on Map
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<CustomSlider
|
||||
geoJsons={geoJsons}
|
||||
setDisplayGeoJsons={setDisplayGeoJsons}
|
||||
setOpenPopup={setOpenPopup}
|
||||
markerValue={sliderValue}
|
||||
minTimestamp={minTimestamp}
|
||||
maxTimestamp={maxTimestamp}
|
||||
replayId={replayId || ""}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Replay;
|
||||
Vendored
Executable
+1
@@ -0,0 +1 @@
|
||||
/// <reference types="react-scripts" />
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
import { ReportHandler } from 'web-vitals';
|
||||
|
||||
const reportWebVitals = (onPerfEntry?: ReportHandler) => {
|
||||
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;
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
// jest-dom adds custom jest matchers for asserting on DOM nodes.
|
||||
// allows you to do things like:
|
||||
// expect(element).toHaveTextContent(/react/i)
|
||||
// learn more: https://github.com/testing-library/jest-dom
|
||||
import '@testing-library/jest-dom';
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
import { createSlice } from "@reduxjs/toolkit";
|
||||
|
||||
const mapSlice = createSlice({
|
||||
name: 'map',
|
||||
initialState: { avgLatLng : { lat: 0, lng: 0 } },
|
||||
reducers: {
|
||||
setAvgLatLng(state, action) {
|
||||
state.avgLatLng = action.payload;
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
export const { setAvgLatLng } = mapSlice.actions
|
||||
|
||||
export default mapSlice.reducer
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
import { createSlice } from "@reduxjs/toolkit";
|
||||
import { User } from '../types/user';
|
||||
|
||||
const user = {
|
||||
name: ''
|
||||
} as User;
|
||||
|
||||
const userSlice = createSlice({
|
||||
name: 'user',
|
||||
initialState: user ? user : null,
|
||||
reducers: {
|
||||
setUser(state, action) {
|
||||
if(user) {
|
||||
if (state) {
|
||||
state.name = action.payload.name;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export const { setUser } = userSlice.actions
|
||||
|
||||
export default userSlice.reducer
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
/* /home/nino-pinzer/devel/Arbeit/rme-playback/frontend/src/styles/map.css */
|
||||
|
||||
.leaflet-container {
|
||||
width: 100%;
|
||||
height: 80vh;
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.leaflet-control {
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.leaflet-popup-content-wrapper {
|
||||
font-size: 14px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.leaflet-popup-tip-container {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.leaflet-popup-content {
|
||||
margin: 8px;
|
||||
}
|
||||
|
||||
.leaflet-marker-icon {
|
||||
z-index: 500;
|
||||
}
|
||||
Vendored
Executable
+12
@@ -0,0 +1,12 @@
|
||||
import { LatLngExpression, Point } from "leaflet";
|
||||
|
||||
export type GeoJson = {
|
||||
type: "Feature";
|
||||
geometry: {
|
||||
type: "Point" | "LineString" | "Polygon";
|
||||
coordinates: LatLngExpression | LatLngExpression[] | LatLngExpression[][];
|
||||
};
|
||||
properties: {
|
||||
[key: string]: any;
|
||||
};
|
||||
};
|
||||
Vendored
Executable
+6
@@ -0,0 +1,6 @@
|
||||
export interface Marker {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
timestamp: number;
|
||||
}
|
||||
Vendored
Executable
+17
@@ -0,0 +1,17 @@
|
||||
import { GeoJson } from "./geojson";
|
||||
|
||||
export type Replay = {
|
||||
name: string;
|
||||
uuid: string;
|
||||
created_by: string;
|
||||
time_created: number;
|
||||
};
|
||||
|
||||
|
||||
export type ReplayInitialInfo = {
|
||||
timestamps: {
|
||||
min_timestamp: number;
|
||||
max_timestamp: number;
|
||||
},
|
||||
geojsons: GeoJson[]
|
||||
}
|
||||
Vendored
Executable
+3
@@ -0,0 +1,3 @@
|
||||
export interface User {
|
||||
name: string;
|
||||
}
|
||||
Executable
+16
@@ -0,0 +1,16 @@
|
||||
export const findMinMax = (timestamps: number[]) => {
|
||||
let minTimestamp = Number.POSITIVE_INFINITY;
|
||||
let maxTimestamp = Number.NEGATIVE_INFINITY;
|
||||
|
||||
for (const timestamp of timestamps) {
|
||||
if (timestamp < minTimestamp) {
|
||||
minTimestamp = timestamp;
|
||||
}
|
||||
if (timestamp > maxTimestamp) {
|
||||
maxTimestamp = timestamp;
|
||||
}
|
||||
}
|
||||
|
||||
return { minTimestamp, maxTimestamp };
|
||||
};
|
||||
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
import ms from 'milsymbol';
|
||||
import { GeoJson } from '../types/geojson';
|
||||
import L from 'leaflet';
|
||||
|
||||
export const getIcon = (marker : GeoJson) => {
|
||||
const symbolOptions: ms.SymbolOptions = {
|
||||
size: 35,
|
||||
sidc: marker.properties.sidic,
|
||||
dtg: marker.properties.dtg || "1054",
|
||||
// additionalInformation: marker.properties.additionalInformation || marker.properties.id,
|
||||
// location: marker.properties.location || "Location",
|
||||
// direction: marker.properties.direction || "Direction",
|
||||
// speed: marker.properties.speed || "Speed",
|
||||
uniqueDesignation: marker.properties.uniqueDesignation || "1",
|
||||
// type: marker.properties.type || "Type",
|
||||
// altitudeDepth: marker.properties.altitudeDepth || "Altitude/Depth",
|
||||
// quantity: marker.properties.quantity || "Quantity",
|
||||
// equipmentTeardownTime: marker.properties.equipmentTeardownTime || "Equipment Teardown Time",
|
||||
// commonIdentifier: marker.properties.commonIdentifier || "Common Identifier",
|
||||
};
|
||||
|
||||
const symbol = new ms.Symbol(symbolOptions);
|
||||
|
||||
const canvas = document.createElement('canvas');
|
||||
const dimensions = symbol.getSize();
|
||||
|
||||
canvas.width = dimensions.width;
|
||||
canvas.height = dimensions.height;
|
||||
|
||||
const ctx = canvas.getContext('2d');
|
||||
if (ctx) {
|
||||
ctx.drawImage(symbol.asCanvas(), 0, 0, canvas.width, canvas.height);
|
||||
}
|
||||
|
||||
const iconUrl = canvas.toDataURL();
|
||||
|
||||
return L.icon({
|
||||
iconUrl: iconUrl,
|
||||
iconSize: [dimensions.width, dimensions.height], // Dynamische Größe verwenden
|
||||
iconAnchor: [dimensions.width / 2, dimensions.height / 2],
|
||||
});
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
export {};
|
||||
|
||||
onmessage = function (e) {
|
||||
const { geoJsons, timestamp, filterParty } = e.data;
|
||||
|
||||
const latestById = geoJsons.reduce((acc: { [x: string]: any; }, geoJson: { properties: { id: any; timestamp: any; }; }) => {
|
||||
const { id, timestamp: geoJsonTimestamp } = geoJson.properties;
|
||||
const time = new Date(geoJsonTimestamp).getTime();
|
||||
|
||||
if (time <= timestamp && (!acc[id] || new Date(acc[id].properties.timestamp).getTime() < time)) {
|
||||
acc[id] = geoJson;
|
||||
}
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
const result = filterParty
|
||||
? (Object.values(latestById) as { properties: any }[]).filter(({ properties }) => properties.parties === filterParty)
|
||||
: Object.values(latestById);
|
||||
|
||||
postMessage(result);
|
||||
};
|
||||
Reference in new issue
Block a user