- 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)
157 lines
5.9 KiB
TypeScript
Executable File
157 lines
5.9 KiB
TypeScript
Executable File
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;
|