Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 42 additions & 4 deletions src/Log4YM.Web/src/plugins/AnalogClockPlugin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,51 @@ export function AnalogClockPlugin() {
const [time, setTime] = useState(new Date());
const containerRef = useRef<HTMLDivElement>(null);
const { stationGrid } = useAppStore();
const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null);

// Update time every second
// Update time every second, but only when page is visible
useEffect(() => {
const interval = setInterval(() => {
const updateTime = () => {
setTime(new Date());
}, 1000);
return () => clearInterval(interval);
};

const startInterval = () => {
// Clear any existing interval
if (intervalRef.current) {
clearInterval(intervalRef.current);
}
// Start new interval
intervalRef.current = setInterval(updateTime, 1000);
};

const stopInterval = () => {
if (intervalRef.current) {
clearInterval(intervalRef.current);
intervalRef.current = null;
}
};

const handleVisibilityChange = () => {
if (document.hidden) {
stopInterval();
} else {
updateTime(); // Update immediately when becoming visible
startInterval();
}
};

// Start interval initially if page is visible
if (!document.hidden) {
startInterval();
}

// Listen for visibility changes
document.addEventListener('visibilitychange', handleVisibilityChange);

return () => {
stopInterval();
document.removeEventListener('visibilitychange', handleVisibilityChange);
};
}, []);

// Calculate sunrise/sunset times
Expand Down
40 changes: 20 additions & 20 deletions src/Log4YM.Web/src/plugins/GlobePlugin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,6 @@ interface GlobeInstance {
export function GlobeCore({ hideOverlays, hideCompass }: { hideOverlays?: boolean; hideCompass?: boolean }) {
const containerRef = useRef<HTMLDivElement>(null);
const globeRef = useRef<GlobeInstance | null>(null);
const animationRef = useRef<number | null>(null);
const cameraAnimationRef = useRef<number | null>(null);
const lastTargetCoordsRef = useRef<{ lat: number; lng: number } | null>(null);

Expand All @@ -121,6 +120,7 @@ export function GlobeCore({ hideOverlays, hideCompass }: { hideOverlays?: boolea
const [currentAzimuth, setCurrentAzimuth] = useState(0);
const [webglError, setWebglError] = useState<string | null>(null);
const [containerHeight, setContainerHeight] = useState(0);
const [globeReady, setGlobeReady] = useState(false);

// Get current radio state if connected
const selectedRadioState = selectedRadioId ? radioStates.get(selectedRadioId) : null;
Expand Down Expand Up @@ -216,10 +216,6 @@ export function GlobeCore({ hideOverlays, hideCompass }: { hideOverlays?: boolea
const numSegments = 50;
const maxDistance = 18000;

// Subtle pulse effect for rotator beam
const pulseTime = Date.now() / 3000;
const pulseFactor = 0.6 + Math.sin(pulseTime * Math.PI * 2) * 0.1;

// Render rotator beam only when connected
if (isConnected) {
// Create left and right edge paths (amber accent)
Expand All @@ -240,7 +236,7 @@ export function GlobeCore({ hideOverlays, hideCompass }: { hideOverlays?: boolea

pathsData.push({
path: pathPoints,
color: `rgba(255, 180, 50, ${pulseFactor})`, // Amber accent for edges
color: 'rgba(255, 180, 50, 0.6)', // Amber accent for edges
stroke: 3
});
}
Expand Down Expand Up @@ -289,11 +285,7 @@ export function GlobeCore({ hideOverlays, hideCompass }: { hideOverlays?: boolea
.ringsData([]);
}, [stationLat, stationLon, getDestinationPoint]);

// Animation loop - use ref to avoid dependency on currentAzimuth
const currentAzimuthRef = useRef(currentAzimuth);
currentAzimuthRef.current = currentAzimuth;

// Track rotator enabled status in ref for animation loop
// Track rotator enabled status in ref for click handler (avoids stale closure)
const rotatorEnabledRef = useRef(rotatorEnabled);
rotatorEnabledRef.current = rotatorEnabled;

Expand All @@ -308,11 +300,6 @@ export function GlobeCore({ hideOverlays, hideCompass }: { hideOverlays?: boolea
const commandedAzimuthRef = useRef<number | null>(null);
const displayedAzimuthRef = useRef<number>(0);

const animateBeam = useCallback(() => {
renderBeam(currentAzimuthRef.current, rotatorEnabledRef.current);
animationRef.current = requestAnimationFrame(animateBeam);
}, [renderBeam]);

// Initialize globe - only runs once on mount
useEffect(() => {
if (!containerRef.current) return;
Expand Down Expand Up @@ -509,7 +496,7 @@ export function GlobeCore({ hideOverlays, hideCompass }: { hideOverlays?: boolea
resizeObserver.observe(containerRef.current);
window.addEventListener('resize', debouncedResize);
setTimeout(handleResize, 100);
animationRef.current = requestAnimationFrame(animateBeam);
setGlobeReady(true);

} catch (e) {
if (!isCancelled) {
Expand All @@ -524,6 +511,8 @@ export function GlobeCore({ hideOverlays, hideCompass }: { hideOverlays?: boolea
// Cleanup function
return () => {
isCancelled = true;
setGlobeReady(false);
globeRef.current = null;
if (resizeObserver) {
resizeObserver.disconnect();
}
Expand All @@ -533,13 +522,24 @@ export function GlobeCore({ hideOverlays, hideCompass }: { hideOverlays?: boolea
if (resizeTimeout) {
clearTimeout(resizeTimeout);
}
if (animationRef.current) {
cancelAnimationFrame(animationRef.current);
}
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [stationLat, stationLon, stationGrid]);

// Render the beam whenever its inputs change. Replaces the old per-frame rAF
// loop that drove the GPU helper to >100% CPU even while idle.
useEffect(() => {
if (!globeReady) return;
renderBeam(currentAzimuth, rotatorEnabled);
}, [
globeReady,
currentAzimuth,
rotatorEnabled,
focusedCallsignInfo?.latitude,
focusedCallsignInfo?.longitude,
renderBeam,
]);

// Update beam when rotator position changes
useEffect(() => {
if (rotatorPosition?.currentAzimuth != null && typeof rotatorPosition.currentAzimuth === 'number') {
Expand Down
38 changes: 36 additions & 2 deletions src/Log4YM.Web/src/plugins/HeaderPlugin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,42 @@ export function HeaderPlugin() {
}, []);

useEffect(() => {
const timer = setInterval(() => setCurrentTime(new Date()), 1000);
return () => clearInterval(timer);
let timer: ReturnType<typeof setInterval> | null = null;

const updateTime = () => setCurrentTime(new Date());

const startTimer = () => {
if (timer) clearInterval(timer);
timer = setInterval(updateTime, 1000);
};

const stopTimer = () => {
if (timer) {
clearInterval(timer);
timer = null;
}
};

const handleVisibilityChange = () => {
if (document.hidden) {
stopTimer();
} else {
updateTime(); // Update immediately when becoming visible
startTimer();
}
};

// Start timer initially if page is visible
if (!document.hidden) {
startTimer();
}

document.addEventListener('visibilitychange', handleVisibilityChange);

return () => {
stopTimer();
document.removeEventListener('visibilitychange', handleVisibilityChange);
};
}, []);

useEffect(() => {
Expand Down
35 changes: 32 additions & 3 deletions src/Log4YM.Web/src/plugins/LogEntryPlugin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -133,12 +133,41 @@
setQsoDate(formatDateForInput(now));
setQsoTime(formatTimeForInput(now));
};
updateTime();
timeIntervalRef.current = setInterval(updateTime, 1000);
return () => {

const startInterval = () => {
if (timeIntervalRef.current) {
clearInterval(timeIntervalRef.current);
}
timeIntervalRef.current = setInterval(updateTime, 1000);
};

const stopInterval = () => {
if (timeIntervalRef.current) {
clearInterval(timeIntervalRef.current);
timeIntervalRef.current = null;
}
};

const handleVisibilityChange = () => {
if (document.hidden) {
stopInterval();
} else {
updateTime(); // Update immediately when becoming visible
startInterval();
}
};

// Update immediately and start interval if page is visible
updateTime();
if (!document.hidden) {
startInterval();
}

document.addEventListener('visibilitychange', handleVisibilityChange);

return () => {
stopInterval();
document.removeEventListener('visibilitychange', handleVisibilityChange);
};
}
}, [timeLocked]);
Expand Down Expand Up @@ -187,7 +216,7 @@
// Clear the selected spot after processing to allow re-selection of same spot
setSelectedSpot(null);
}
}, [selectedSpot, setSelectedSpot]);

Check warning on line 219 in src/Log4YM.Web/src/plugins/LogEntryPlugin.tsx

View workflow job for this annotation

GitHub Actions / Frontend Tests (React)

React Hook useEffect has a missing dependency: 'formData.mode'. Either include it or remove the dependency array

// Update RST defaults when mode changes
useEffect(() => {
Expand Down
Loading