Skip to content
Merged
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
29 changes: 26 additions & 3 deletions apps/frontend/src/app/components/AssetsDropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,13 @@ import { toggleAssetLayer } from './MapView';
import { Map } from 'ol';
import '../styles/AssetsDropdown.css';

/**
* A dropdown menu for displaying and toggling weather-related asset layers
* like humidity, wind force, and wind direction on the map.
*
* This component is conditionally rendered when a dataset is loaded
* and no processing is ongoing.
*/
const AssetsDropdown = () => {
const [assetsMenuOpen, setAssetsMenuOpen] = useState(false);
const { t } = useTranslation();
Expand All @@ -24,7 +31,12 @@ const AssetsDropdown = () => {
isProcessLoading,
} = useMapLayerContext();

// Function to get the appropriate icon for a layer
/**
* Returns an appropriate icon based on the layer name.
*
* @param layerName - The name of the asset layer
* @returns A React icon component for the layer
*/
const getLayerIcon = (layerName: string) => {
switch (layerName) {
case 'humidity':
Expand All @@ -38,15 +50,26 @@ const AssetsDropdown = () => {
}
};

// Function to format layer name
/**
* Formats asset layer names by replacing underscores with spaces
* and capitalizing the first letter of each word.
*
* @param name - The raw layer name string
* @returns A formatted layer name
*/
const formatLayerName = (name: string): string => {
return name
.split('_')
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
.join(' ');
};

// Handles clicking on an asset layer button
/**
* Handles the user clicking an asset layer button.
* Toggles the visibility of the layer on the map.
*
* @param layerName - The name of the asset layer to toggle
*/
const handleLayerClick = (layerName: string) => {
const map = mapRef.current as Map;
if (!map) {
Expand Down
63 changes: 57 additions & 6 deletions apps/frontend/src/app/components/Footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ import {
} from '../services/api';
import { changeLayer, toggleAssetLayer } from './MapView';

/**
* Footer component responsible for playback controls, slider timeline,
* and playback speed adjustments for temporal dataset visualization.
*/
const Footer = () => {
const { t } = useTranslation();
const {
Expand Down Expand Up @@ -42,6 +46,12 @@ const Footer = () => {

const speedValues = [0.25, 0.5, 1, 1.5, 2];

/**
* Loads asset layers for the selected timestamp item ID,
* and toggles selected layers on the map accordingly.
*
* @param itemId - The STAC item ID used to load related asset layers.
*/
const processLoadedLayers = async (itemId: string) => {
setLoadedLayers([]);
if (!isProcessLoading) {
Expand Down Expand Up @@ -72,6 +82,9 @@ const Footer = () => {
if (!isProcessLoading) processLoadedLayers(itemIds[sliderValue]);
}, [isProcessLoading]);

/**
* Loads saved playback speed from localStorage on mount.
*/
useEffect(() => {
if (typeof window !== 'undefined') {
try {
Expand All @@ -92,6 +105,9 @@ const Footer = () => {
}
}, []);

/**
* Saves playback speed to localStorage whenever it changes.
*/
useEffect(() => {
if (speedInitialized && typeof window !== 'undefined') {
try {
Expand All @@ -103,14 +119,25 @@ const Footer = () => {
}
}, [speed, speedInitialized]);

/**
* Handles play/pause toggle.
*/
const handlePlayPause = () => {
if (timeStamps.length === 0) return;
setIsPlaying((prev) => !prev);
};

/**
* Changes the playback speed to the selected value.
* @param newSpeed - The new speed multiplier.
*/
const handleSpeedChange = (newSpeed: number) => {
setSpeed(newSpeed);
};

/**
* Initiates dragging behavior on the slider.
*/
const handleMouseDown = (e: React.MouseEvent) => {
isDraggingRef.current = true;
handleSliderMove(e);
Expand All @@ -124,14 +151,17 @@ const Footer = () => {
}
};

// Set global slider value when mouse up from sliding timeline
/**
* Ends dragging behavior and updates actual slider value.
* Set global slider value when mouse up from sliding timeline
*/
const handleMouseUp = () => {
isDraggingRef.current = false;
document.removeEventListener('mousemove', handleMouseMove);
document.removeEventListener('mouseup', handleMouseUp);

const finalValue = pendingSliderRef.current;

setSliderValue(finalValue);
const map = mapRef.current as Map;
if (timeStamps.length > 0) {
Expand All @@ -140,6 +170,9 @@ const Footer = () => {
}
};

/**
* Manages autoplay loop using setInterval.
*/
useEffect(() => {
const map = mapRef.current as Map;

Expand All @@ -159,7 +192,10 @@ const Footer = () => {
return () => clearInterval(intervalRef.current!);
}, [isPlaying, speed, sliderValue, timeStamps]);

// Change temp slider value to lessen load on backend calls
/**
* Handles the dragging of the slider and updates pending value.
* Change temp slider value to lessen load on backend calls
*/
const handleSliderMove = (e: MouseEvent | React.MouseEvent) => {
if (sliderRef.current && timeStamps.length > 0) {
const rect = sliderRef.current.getBoundingClientRect();
Expand All @@ -171,12 +207,15 @@ const Footer = () => {
timeStamps.length - 1,
),
);

setPendingSliderValue(newValue);
pendingSliderRef.current = newValue;
}
};

/**
* Stops the playback and resets to the first timestamp.
*/
const handleStopPress = () => {
const map = mapRef.current as Map;
setIsPlaying(false);
Expand All @@ -185,6 +224,12 @@ const Footer = () => {
changeLayer(map, false, timeStamps[0]);
};

/**
* Formats an ISO timestamp into readable date and time (UTC).
*
* @param timestamp - ISO timestamp string
* @returns JSX element displaying formatted date and time
*/
const formatTimestamp = (timestamp: string) => {
const date = new Date(timestamp);

Expand All @@ -208,6 +253,9 @@ const Footer = () => {
);
};

/**
* Handles spacebar keyboard shortcut for toggling playback.
*/
useEffect(() => {
const handleKeyDown = (event: KeyboardEvent) => {
if (event.code === 'Space') {
Expand All @@ -223,6 +271,7 @@ const Footer = () => {

/**
* This fetches and loads the stac items if they exist in the items table
* Initializes timestamps and item IDs on component mount.
*/
const initializeTimestampIfItemsPresent = async () => {
const timestampsResponse = await fetchTimestamps();
Expand All @@ -247,7 +296,9 @@ const Footer = () => {
}
};

// Handle slider value changes
/**
* Reacts to slider value change by reloading layers and updating map.
*/
useEffect(() => {
const currentTimestamp = timeStamps[sliderValue];

Expand Down
13 changes: 12 additions & 1 deletion apps/frontend/src/app/components/LoadingModule.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,24 @@ import '../styles/LoadingModule.css';
import { useTranslation } from 'react-i18next';
import React from 'react';

/**
* Props for the LoadingModule component.
*/
interface LoadingModuleProps {
datasetBeingLoaded: string | undefined;
progress: number; // Progress percentage
isVisible: boolean; // Toggle visibility
errorMessage?: string; // Error message to display on failure
}

/**
* Displays a loading progress bar with an optional error message.
* Used during dataset loading to indicate current progress or failure.
*
* @component
* @param {LoadingModuleProps} props - Component props
* @returns {JSX.Element | null} The rendered loading module or null if hidden
*/
const LoadingModule: React.FC<LoadingModuleProps> = ({ datasetBeingLoaded, progress, isVisible, errorMessage }) => {
const { t } = useTranslation();
if (!isVisible) return null;
Expand Down Expand Up @@ -36,4 +47,4 @@ const LoadingModule: React.FC<LoadingModuleProps> = ({ datasetBeingLoaded, progr
);
};

export default LoadingModule;
export default LoadingModule;
Loading