From 61da5a4337364fd723a0ba4cbaadcc9ba2e617fb Mon Sep 17 00:00:00 2001 From: eo2000 Date: Thu, 10 Apr 2025 13:31:20 -0400 Subject: [PATCH 1/2] #210 - Adding comments for AssetsDropdown and Footer components --- .../src/app/components/AssetsDropdown.tsx | 29 ++++++++- apps/frontend/src/app/components/Footer.tsx | 63 +++++++++++++++++-- 2 files changed, 83 insertions(+), 9 deletions(-) diff --git a/apps/frontend/src/app/components/AssetsDropdown.tsx b/apps/frontend/src/app/components/AssetsDropdown.tsx index 77df916a..9e5a5b03 100644 --- a/apps/frontend/src/app/components/AssetsDropdown.tsx +++ b/apps/frontend/src/app/components/AssetsDropdown.tsx @@ -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(); @@ -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': @@ -38,7 +50,13 @@ 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('_') @@ -46,7 +64,12 @@ const AssetsDropdown = () => { .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) { diff --git a/apps/frontend/src/app/components/Footer.tsx b/apps/frontend/src/app/components/Footer.tsx index 54cad84d..f34f727e 100644 --- a/apps/frontend/src/app/components/Footer.tsx +++ b/apps/frontend/src/app/components/Footer.tsx @@ -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 { @@ -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) { @@ -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 { @@ -92,6 +105,9 @@ const Footer = () => { } }, []); + /** + * Saves playback speed to localStorage whenever it changes. + */ useEffect(() => { if (speedInitialized && typeof window !== 'undefined') { try { @@ -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); @@ -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) { @@ -140,6 +170,9 @@ const Footer = () => { } }; + /** + * Manages autoplay loop using setInterval. + */ useEffect(() => { const map = mapRef.current as Map; @@ -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(); @@ -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); @@ -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); @@ -208,6 +253,9 @@ const Footer = () => { ); }; + /** + * Handles spacebar keyboard shortcut for toggling playback. + */ useEffect(() => { const handleKeyDown = (event: KeyboardEvent) => { if (event.code === 'Space') { @@ -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(); @@ -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]; From eb39bc6520c1fd8a0e7e6f6819b2f4ff55b31159 Mon Sep 17 00:00:00 2001 From: eo2000 Date: Thu, 10 Apr 2025 13:41:39 -0400 Subject: [PATCH 2/2] #210 - Adding comments for frontend components --- .../src/app/components/LoadingModule.tsx | 13 ++++- .../src/app/components/MapMetaData.tsx | 57 ++++++++++++++---- apps/frontend/src/app/components/MapView.tsx | 7 +++ .../src/app/components/SettingsPanel.tsx | 58 ++++++++++++++++++- apps/frontend/src/app/components/Sidebar.tsx | 19 ++++++ 5 files changed, 140 insertions(+), 14 deletions(-) diff --git a/apps/frontend/src/app/components/LoadingModule.tsx b/apps/frontend/src/app/components/LoadingModule.tsx index 28502179..945ee36c 100644 --- a/apps/frontend/src/app/components/LoadingModule.tsx +++ b/apps/frontend/src/app/components/LoadingModule.tsx @@ -2,6 +2,9 @@ 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 @@ -9,6 +12,14 @@ interface LoadingModuleProps { 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 = ({ datasetBeingLoaded, progress, isVisible, errorMessage }) => { const { t } = useTranslation(); if (!isVisible) return null; @@ -36,4 +47,4 @@ const LoadingModule: React.FC = ({ datasetBeingLoaded, progr ); }; -export default LoadingModule; \ No newline at end of file +export default LoadingModule; diff --git a/apps/frontend/src/app/components/MapMetaData.tsx b/apps/frontend/src/app/components/MapMetaData.tsx index ec66b16f..0720c04e 100644 --- a/apps/frontend/src/app/components/MapMetaData.tsx +++ b/apps/frontend/src/app/components/MapMetaData.tsx @@ -20,6 +20,9 @@ import Swal from 'sweetalert2'; import { getConfig, saveConfig } from '../services/configApi'; import AssetsDropdown from './AssetsDropdown'; +/** + * Props for the MapMetaData component. + */ interface MapMetaDataProps { id?: string; name?: string; @@ -32,6 +35,13 @@ interface MapMetaDataProps { refreshDatasets: () => void; } +/** + * Component for displaying and interacting with metadata and control + * actions related to a selected dataset. + * + * Includes support for dataset loading, progress tracking, asset loading, + * collapsible/resizable UI, and integration with global map context. + */ const MapMetaData: React.FC = ({ id = '', name = '', @@ -51,6 +61,9 @@ const MapMetaData: React.FC = ({ const startXRef = useRef(0); const startWidthRef = useRef(0); const animationRef = useRef(null); // Initialize as null + /** + * Toggles metadata panel collapsed state. + */ const toggleCollapse = () => setIsCollapsed((prev) => !prev); const [loading, setLoading] = useState(false); const [progress, setProgress] = useState(0); @@ -73,43 +86,55 @@ const MapMetaData: React.FC = ({ setIsPlaying } = useMapLayerContext(); + /** + * Mouse down event handler for initiating panel resize. + */ const handleMouseDown = useCallback((e: React.MouseEvent) => { if (!containerRef.current) return; - + setIsResizing(true); startXRef.current = e.clientX; startWidthRef.current = containerRef.current.offsetWidth; - + // Hint browser about upcoming changes for better performance if (containerRef.current) { containerRef.current.style.willChange = 'width'; } - + e.preventDefault(); }, []); + /** + * Mouse move event handler for dynamically resizing the panel. + */ const handleMouseMove = useCallback((e: MouseEvent) => { if (!isResizing || !containerRef.current) return; - + const dx = e.clientX - startXRef.current; let newWidth = startWidthRef.current + dx; - + // Apply constraints newWidth = Math.max(minWidth, Math.min(newWidth, maxWidth)); - + // DIRECT DOM UPDATE (no React state lag) containerRef.current.style.width = `${newWidth}px`; }, [isResizing, maxWidth, minWidth]); + /** + * Mouse up event handler to finalize resize and store final width. + */ const handleMouseUp = useCallback(() => { if (!isResizing || !containerRef.current) return; - + // Only update React state AFTER dragging finishes setWidth(containerRef.current.offsetWidth); containerRef.current.style.willChange = 'auto'; setIsResizing(false); }, [isResizing]); + /** + * Attach/remove mouse event listeners during resizing. + */ useEffect(() => { if (isResizing) { document.addEventListener('mousemove', handleMouseMove); @@ -128,6 +153,11 @@ const MapMetaData: React.FC = ({ }; }, [isResizing, handleMouseMove, handleMouseUp]); + /** + * Loads the selected dataset, clears previous data, + * polls progress endpoints for both items and assets, + * and updates local + server config state. + */ const onLoadDataset = async () => { if (!isOnline) { toast.error(`${t('disabled')} - ${t('no_internet_access')}`, { @@ -190,6 +220,9 @@ const MapMetaData: React.FC = ({ toastId: 'timestamps-success', }); + /** + * Polls the item fetch progress endpoint until it completes or stalls. + */ const pollProgress = async () => { let lastProgress = -1; let stableCount = 0; @@ -256,7 +289,9 @@ const MapMetaData: React.FC = ({ await loadAssets(id); // triggers backend async processing - // Now start polling for progress on the asset load + /** + * Polls the asset fetch progress endpoint until completion. + */ const pollAssetProgress = async () => { // eslint-disable-next-line no-constant-condition while (true) { @@ -322,7 +357,7 @@ const MapMetaData: React.FC = ({ ); const NonCollapsedMetaData = ( -
= ({
{/* Resize handle */} -
= ({ return <>{isCollapsed ? CollapsedMetaData : NonCollapsedMetaData}; }; -export default MapMetaData; \ No newline at end of file +export default MapMetaData; diff --git a/apps/frontend/src/app/components/MapView.tsx b/apps/frontend/src/app/components/MapView.tsx index ccbc2ae8..94cb0e81 100644 --- a/apps/frontend/src/app/components/MapView.tsx +++ b/apps/frontend/src/app/components/MapView.tsx @@ -199,6 +199,13 @@ interface MapViewProps { * @param {MapViewProps} props - Component props including bbox change handler. * @returns {JSX.Element} The rendered map component. */ + +/** + * Main map visualization component using OpenLayers. + * Handles base map initialization, dynamic layer updates (STAC collections/items), + * custom asset overlays, style updates, and bounding box reporting. + */ + const MapView = ({ onBboxChange }: MapViewProps) => { useGeographic(); const mapElement = useRef(null); diff --git a/apps/frontend/src/app/components/SettingsPanel.tsx b/apps/frontend/src/app/components/SettingsPanel.tsx index 01aaba0f..4a2cd536 100644 --- a/apps/frontend/src/app/components/SettingsPanel.tsx +++ b/apps/frontend/src/app/components/SettingsPanel.tsx @@ -32,6 +32,18 @@ import { LuPalette } from 'react-icons/lu'; const MySwal = withReactContent(Swal); +/** + * SettingsPanel component provides global configuration controls including: + * - API endpoint management + * - Language switching + * - Online/offline toggle + * - Reset/factory reset options + * - Layer style customization + * + * @component + * @param {Function} refreshDatasets - Function to refresh dataset list + * @param {Function} setMetadataVisible - Function to toggle metadata panel visibility + */ const SettingsPanel: React.FC<{ refreshDatasets: () => void; setMetadataVisible: (visible: boolean) => void; @@ -132,6 +144,13 @@ const SettingsPanel: React.FC<{ })); }; + /** + * Verifies and saves a new API endpoint, fetches collections from it, + * resets previous data, updates config, and triggers a refresh. + * + * @param endpointUrl - URL to validate and save + * @returns boolean - success or failure of operation + */ const handleSaveAndFetchEndpoint = async ( endpointUrl: string, ): Promise => { @@ -183,12 +202,21 @@ const SettingsPanel: React.FC<{ } }; + /** + * Handles language change and stores selected language in localStorage. + * + * @param language - The selected language key (e.g., 'en', 'fr') + */ const handleLanguageSelect = (language: string) => { i18n.changeLanguage(language); localStorage.setItem('language', language); setDropdownState({ activeButton: null, isOpen: false }); }; + /** + * Resets key application settings (language, speed, slider) to defaults. + * Triggers map layer style reset and toast notification. + */ const handleReset = async () => { MySwal.fire({ title: t('reset'), @@ -219,6 +247,10 @@ const SettingsPanel: React.FC<{ setDropdownState({ activeButton: null, isOpen: false }); }; + /** + * Performs a full factory reset by clearing all collections, localStorage, + * and config settings. Triggers prompt for a new API endpoint after reset. + */ const handleFactoryReset = async () => { if (!isOnline) { toast.error(`${t('disabled')} - ${t('no_internet_access')}`, { @@ -260,6 +292,11 @@ const SettingsPanel: React.FC<{ setDropdownState({ activeButton: null, isOpen: false }); }; + /** + * Toggles between online and offline mode and persists to config. + * + * @param onlineMode - true for online, false for offline + */ const handleSelectOnlineMode = async (onlineMode: boolean) => { setIsOnline(onlineMode); @@ -274,6 +311,10 @@ const SettingsPanel: React.FC<{ setDropdownState({ activeButton: null, isOpen: false }); }; + /** + * Completely resets the app’s local state, config, and storage keys. + * Clears all collections, styles, and map layers. + */ const resetConfig = async () => { try { handleResetLayerStyle(true); @@ -313,6 +354,10 @@ const SettingsPanel: React.FC<{ } }; + /** + * Prompts the user to input and save a valid API endpoint. + * Repeats until a valid endpoint is provided or user cancels. + */ const promptForEndpoint = async ( refreshDatasets: () => void, t: any, @@ -433,7 +478,10 @@ const SettingsPanel: React.FC<{ : 'rgb(0,0,0)'; }; - // Update itemLayer and dataLayer style + /** + * Applies user-selected color, opacity, and stroke settings to map layers. + * Update itemLayer and dataLayer style + */ const handleUpdateLayerStyle = () => { if (!mapRef.current) return; @@ -458,7 +506,13 @@ const SettingsPanel: React.FC<{ } }; - // Reset itemLayer and dataLayer style + + /** + * Resets item or data layer styles to default values. + * Can reset both layers if `resetBoth` is true. + * + * @param resetBoth - Optional. If true, resets both layer styles. + */ const handleResetLayerStyle = (resetBoth = false) => { if (!mapRef.current) return; diff --git a/apps/frontend/src/app/components/Sidebar.tsx b/apps/frontend/src/app/components/Sidebar.tsx index c90ac252..d11ec29e 100644 --- a/apps/frontend/src/app/components/Sidebar.tsx +++ b/apps/frontend/src/app/components/Sidebar.tsx @@ -12,15 +12,31 @@ const terrainImage = '/assets/Terrain_layer.png'; import { toast } from 'react-toastify'; import 'react-toastify/dist/ReactToastify.css'; +/** + * Sidebar component used to switch between map base layers (default, topographical, satellite). + * The panel can be collapsed/expanded and is disabled when offline for non-default layers. + * + * @component + * @returns {JSX.Element} Sidebar for selecting map layers + */ const Sidebar = () => { const { t } = useTranslation(); const [isCollapsed, setIsCollapsed] = useState(true); const { setLayer, isOnline } = useMapLayerContext(); + /** + * Toggles the collapsed state of the sidebar. + */ const toggleCollapse = () => { setIsCollapsed((prev) => !prev); }; + /** + * Handles user request to switch base map layers. + * Only allows non-default layers if the app is online. + * + * @param {string} layerName - The name of the layer to activate. + */ const handleLayerChange = (layerName: string) => { if(!isOnline && layerName != 'default'){ toast.error(`${t('disabled')} - ${t('no_internet_access')}`, { @@ -31,6 +47,9 @@ const Sidebar = () => { setLayer(layerName); }; + /** + * Automatically switches back to the default layer if the app goes offline. + */ useEffect(() => { if(!isOnline) handleLayerChange('default');