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
+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;
|
||||
Reference in new issue
Block a user