diff --git a/.eslintrc b/.eslintrc index d6de011f..593a1f77 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,10 +1,18 @@ { - "parserOptions": { "project": ["./tsconfig.json"] }, - "extends": ["react-app"], - "ignorePatterns": ["vite.config.*", "commitlint.config.js"], - "plugins": ["react"], - "rules": { - "import/exports-last": 0, - "no-lonely-if": 0 + "parserOptions": { "project": ["./tsconfig.json"] }, + "extends": ["react-app"], + "ignorePatterns": ["vite.config.*", "commitlint.config.js"], + "plugins": ["react"], + "rules": { + "import/exports-last": 0, + "no-lonely-if": 0 + }, + "overrides": [ + { + "files": ["packages/react-components/**/*.{ts,tsx,js,jsx}"], + "rules": { + "react-hooks/exhaustive-deps": "off" + } } + ] } diff --git a/packages/react-components/src/components/autocomplete/autocomplete.stories.tsx b/packages/react-components/src/components/autocomplete/autocomplete.stories.tsx index ce883c7f..a09911b4 100644 --- a/packages/react-components/src/components/autocomplete/autocomplete.stories.tsx +++ b/packages/react-components/src/components/autocomplete/autocomplete.stories.tsx @@ -1,5 +1,3 @@ -/* eslint-disable @typescript-eslint/naming-convention, @typescript-eslint/prefer-regexp-exec, @typescript-eslint/no-magic-numbers*/ -import React from 'react'; import type { StoryFn } from '@storybook/react'; import { TextField } from '@map-colonies/react-core'; import { CSFStory } from '../utils/story'; @@ -7,14 +5,14 @@ import { CSFStory } from '../utils/story'; import Autocomplete from './autocomplete'; const meta = { - title: 'Autocomplete', + title: 'Components/Autocomplete', component: Autocomplete, }; export default meta; -export const AutocpmleteTextArea: CSFStory = () => ( +export const AutocompleteTextArea: CSFStory = () => ( <> -

Autocpmlete with native HTML TEXTAREA

+

Autocomplete with native HTML TEXTAREA

= () => ( /> ); -AutocpmleteTextArea.story = { - name: 'Autocpmlete with TEXTAREA HTML', +AutocompleteTextArea.story = { + name: 'Autocomplete with TEXTAREA HTML', }; -export const AutocpmleteTextField: StoryFn = (args: Record) => { +export const AutocompleteTextField: StoryFn = (args: Record) => { return ( <> -

Autocpmlete with TEXTFIELD react-core component

+

Autocomplete with TEXTFIELD react-core component

, @@ -43,8 +41,8 @@ export const AutocpmleteTextField: StoryFn = (args: Record) => ); }; -AutocpmleteTextField.storyName = 'Autocpmlete with TEXTFIELD component'; -AutocpmleteTextField.argTypes = { +AutocompleteTextField.storyName = 'Autocomplete with TEXTFIELD component'; +AutocompleteTextField.argTypes = { disabled: { defaultValue: false, control: { @@ -65,10 +63,10 @@ AutocpmleteTextField.argTypes = { }, }; -export const AutocpmleteInComplitionModeEN: StoryFn = (args: Record) => { +export const AutocompleteInCompletionModeEN: StoryFn = (args: Record) => { return ( <> -

Autocpmlete with TEXTFIELD in AUTOCOMPLETE mode in English (LTR)

+

Autocomplete with TEXTFIELD in AUTOCOMPLETE mode in English (LTR)

, @@ -80,12 +78,12 @@ export const AutocpmleteInComplitionModeEN: StoryFn = (args: Record ); }; -AutocpmleteInComplitionModeEN.storyName = 'Autocpmlete in autocomplete MODE RTL'; +AutocompleteInCompletionModeEN.storyName = 'Autocomplete in autocomplete MODE RTL'; -export const AutocpmleteInComplitionModeHEB: StoryFn = (args: Record) => { +export const AutocompleteInCompletionModeHEB: StoryFn = (args: Record) => { return (
-

Autocpmlete with TEXTFIELD in AUTOCOMPLETE mode in Hebrew (RTL)

+

Autocomplete with TEXTFIELD in AUTOCOMPLETE mode in Hebrew (RTL)

, @@ -97,4 +95,4 @@ export const AutocpmleteInComplitionModeHEB: StoryFn = (args: Record ); }; -AutocpmleteInComplitionModeHEB.storyName = 'Autocpmlete in autocomplete MODE LTR'; +AutocompleteInCompletionModeHEB.storyName = 'Autocomplete in autocomplete MODE LTR'; diff --git a/packages/react-components/src/components/cesium-map/active-layers/active-layers-panel.tsx b/packages/react-components/src/components/cesium-map/active-layers/active-layers-panel.tsx index 6f1510ea..93ee716c 100644 --- a/packages/react-components/src/components/cesium-map/active-layers/active-layers-panel.tsx +++ b/packages/react-components/src/components/cesium-map/active-layers/active-layers-panel.tsx @@ -1,6 +1,6 @@ import { Cesium3DTileset, Rectangle } from 'cesium'; import { get } from 'lodash'; -import React, { useEffect, useState } from 'react'; +import React, { useCallback, useEffect, useState } from 'react'; import { Tooltip, Typography } from '@map-colonies/react-core'; import bbox from '@turf/bbox'; import { Box } from '../../box'; @@ -145,7 +145,7 @@ export const ActiveLayersPanel: React.FC = ({ locale }) }); }; - const refreshSections = (): void => { + const refreshSections = useCallback((): void => { setSections([ { id: IMAGERY, @@ -164,7 +164,7 @@ export const ActiveLayersPanel: React.FC = ({ locale }) values: get3DModels(), }, ]); - }; + }, []); useEffect(() => { refreshSections(); diff --git a/packages/react-components/src/components/cesium-map/base-map/base-map-widget.tsx b/packages/react-components/src/components/cesium-map/base-map/base-map-widget.tsx index bb10cc7d..cbc42734 100644 --- a/packages/react-components/src/components/cesium-map/base-map/base-map-widget.tsx +++ b/packages/react-components/src/components/cesium-map/base-map/base-map-widget.tsx @@ -1,4 +1,4 @@ -import React, { useMemo, useRef, useState } from 'react'; +import React, { useMemo, useState } from 'react'; import { get } from 'lodash'; import { IBaseMap, IBaseMaps, ITerrain } from '../map'; import { CesiumIcon } from '../widget/cesium-icon'; diff --git a/packages/react-components/src/components/cesium-map/context-menu.stories.tsx b/packages/react-components/src/components/cesium-map/context-menu.stories.tsx index e0b82865..20e935a8 100644 --- a/packages/react-components/src/components/cesium-map/context-menu.stories.tsx +++ b/packages/react-components/src/components/cesium-map/context-menu.stories.tsx @@ -126,12 +126,10 @@ const ContextMenu: React.FC = ({ data, position, style, handle top: `${position.y}px`, }; - /* eslint-disable react-hooks/exhaustive-deps */ useEffect(() => { setPickedLayers(mapViewer.layersManager?.pickImageryLayers(position as CesiumCartesian2)); console.log(mapViewer.layersManager?.pickImageryLayers(position as CesiumCartesian2)); }, [position]); - /* eslint-enable react-hooks/exhaustive-deps */ return ( <> diff --git a/packages/react-components/src/components/cesium-map/data-sources/drawings.data-source.tsx b/packages/react-components/src/components/cesium-map/data-sources/drawings.data-source.tsx index 76ad94c6..372488bc 100644 --- a/packages/react-components/src/components/cesium-map/data-sources/drawings.data-source.tsx +++ b/packages/react-components/src/components/cesium-map/data-sources/drawings.data-source.tsx @@ -118,7 +118,6 @@ export const CesiumDrawingsDataSource: React.FC = break; default: throw new Error(`[CESIUM DRAW]: ${drawState.type} unrecognized primitive to draw.`); - break; } } else { // eslint-disable-next-line diff --git a/packages/react-components/src/components/cesium-map/helpers/utils.ts b/packages/react-components/src/components/cesium-map/helpers/utils.ts index 01eb5230..460352dd 100644 --- a/packages/react-components/src/components/cesium-map/helpers/utils.ts +++ b/packages/react-components/src/components/cesium-map/helpers/utils.ts @@ -26,7 +26,6 @@ import area from '@turf/area'; import bboxPolygon from '@turf/bbox-polygon'; import centroid from '@turf/centroid'; import { point } from '@turf/helpers'; -import * as turf from '@turf/helpers'; import intersect from '@turf/intersect'; import pointToPolygonDistance from '@turf/point-to-polygon-distance'; import { ICesiumWFSLayerLabelingOptions } from '../layers'; @@ -404,7 +403,6 @@ export const defaultVisualizationHandler = ( return [correctedCarto.longitude, correctedCarto.latitude, correctedCarto.height]; }); - // return turf.polygon(positions); return { type: 'Polygon', coordinates: [positions], diff --git a/packages/react-components/src/components/cesium-map/layers/3d.tileset.with.update.tsx b/packages/react-components/src/components/cesium-map/layers/3d.tileset.with.update.tsx index 48472de4..aaef90a0 100644 --- a/packages/react-components/src/components/cesium-map/layers/3d.tileset.with.update.tsx +++ b/packages/react-components/src/components/cesium-map/layers/3d.tileset.with.update.tsx @@ -37,7 +37,6 @@ export const Cesium3DTilesetWithUpdate: React.FC = ( return () => { cancelled = true; }; - // eslint-disable-next-line react-hooks/exhaustive-deps }, [url]); useEffect(() => { diff --git a/packages/react-components/src/components/cesium-map/layers/layers.rect.stories.tsx b/packages/react-components/src/components/cesium-map/layers/layers.rect.stories.tsx index 597e1c2c..0ea9b971 100644 --- a/packages/react-components/src/components/cesium-map/layers/layers.rect.stories.tsx +++ b/packages/react-components/src/components/cesium-map/layers/layers.rect.stories.tsx @@ -1,5 +1,5 @@ import { ImageryLayer, Rectangle } from 'cesium'; -import React, { useLayoutEffect, useMemo } from 'react'; +import React, { useEffect, useLayoutEffect, useMemo } from 'react'; import type { StoryFn, Meta } from '@storybook/react'; import bbox from '@turf/bbox'; import { BASE_MAPS } from '../helpers/constants'; @@ -64,6 +64,17 @@ const layerMetaRectXYZ = { searchLayerPredicate: (layer: ImageryLayer): boolean => getImageryProviderUrl(layer) === optionsRectXYZ.url, }; +const layerRaster = { + id: '2_raster_ext', + type: 'XYZ_LAYER' as LayerType, + opacity: 1, + zIndex: 0, + show: true, + options: { + url: 'https://tiles.openaerialmap.org/5a9f90c42553e6000ce5ad6c/0/eee1a570-128e-4947-9ffa-1e69c1efab7c/{z}/{x}/{y}.png', + }, +}; + export const MapWithXYZLayersAndRect: StoryFn = () => (
@@ -75,21 +86,10 @@ export const MapWithXYZLayersAndRect: StoryFn = () => ( MapWithXYZLayersAndRect.storyName = 'XYZ child layer with rect'; export const MapWithSettings: StoryFn = () => { - const layer = { - id: '2_raster_ext', - type: 'XYZ_LAYER' as LayerType, - opacity: 1, - zIndex: 0, - show: true, - options: { - url: 'https://tiles.openaerialmap.org/5a9f90c42553e6000ce5ad6c/0/eee1a570-128e-4947-9ffa-1e69c1efab7c/{z}/{x}/{y}.png', - }, - }; - return (
- +
); @@ -105,9 +105,12 @@ const LayerViewer: React.FC = (props) => { const { layer } = props; // For testing the exposure of current zoom level on map viewer - setInterval(() => { - console.log('######################### Zoom level: ', (mapViewer as { currentZoomLevel?: number }).currentZoomLevel); - }, 2000); + useEffect(() => { + const interval = window.setInterval(() => { + console.log('######################### Zoom level: ', (mapViewer as { currentZoomLevel?: number }).currentZoomLevel); + }, 2000); + return () => window.clearInterval(interval); + }, [mapViewer]); // Mockin footprint data on layer meta const layerFootprint = useMemo( @@ -132,6 +135,10 @@ const LayerViewer: React.FC = (props) => { layer.options.rectangle = layerManagerRect; mapViewer.layersManager?.addRasterLayer(layer, 0, ''); + + return () => { + mapViewer.layersManager?.removeLayer(layer.id); + }; }, [mapViewer, layerFootprint, layer]); return <>; diff --git a/packages/react-components/src/components/cesium-map/layers/optimized-tile-requests.stories.tsx b/packages/react-components/src/components/cesium-map/layers/optimized-tile-requests.stories.tsx index 55a6aafc..7f57ea52 100644 --- a/packages/react-components/src/components/cesium-map/layers/optimized-tile-requests.stories.tsx +++ b/packages/react-components/src/components/cesium-map/layers/optimized-tile-requests.stories.tsx @@ -106,7 +106,9 @@ const LayersContainer: React.FC = () => { meta={{ id: 'Opaque Layer', options: { ...optionsXYZOpaque }, - searchLayerPredicate: (layer: ImageryLayer): boolean => getImageryProviderUrl(layer) === optionsXYZOpaque.url, + searchLayerPredicate: (layer: ImageryLayer): boolean => { + return getImageryProviderUrl(layer) === optionsXYZOpaque.url; + }, }} rectangle={Rectangle.fromDegrees(...bbox(optionsXYZOpaque.footprint))} options={optionsXYZOpaque} diff --git a/packages/react-components/src/components/cesium-map/layers/wfs.layer.stories.tsx b/packages/react-components/src/components/cesium-map/layers/wfs.layer.stories.tsx index 0e973928..00db39d0 100644 --- a/packages/react-components/src/components/cesium-map/layers/wfs.layer.stories.tsx +++ b/packages/react-components/src/components/cesium-map/layers/wfs.layer.stories.tsx @@ -283,6 +283,7 @@ const optionsPolygonParts = { labeling: { dataSourcePrefix: 'labels_', text: { + // eslint-disable-next-line no-template-curly-in-string pattern: '${imagingTimeEndUTC}\n v${productVersion} (${resolutionDegree})', fields: [ { diff --git a/packages/react-components/src/components/cesium-map/layers/wfs.layer.tsx b/packages/react-components/src/components/cesium-map/layers/wfs.layer.tsx index 9227743a..6dc24504 100644 --- a/packages/react-components/src/components/cesium-map/layers/wfs.layer.tsx +++ b/packages/react-components/src/components/cesium-map/layers/wfs.layer.tsx @@ -598,6 +598,7 @@ export const CesiumWFSLayer: React.FC = (props) => { const queryName = payload.query.split('(')[0].replace('query ', ''); //strip queryName await handleWfsResponse((wfsResponse as any)?.data[queryName], extent, offset, position); } else { + // eslint-disable-next-line no-throw-literal throw 'API as alternative service still not supported'; } } else { @@ -690,11 +691,14 @@ export const CesiumWFSLayer: React.FC = (props) => { hasRunFetchRef.current = true; } + const wfsCacheRef = wfsCache.current; + const fetchMetadataRef = fetchMetadata.current; + // Cleanup return () => { if (get(mapViewer, '_cesiumWidget') !== undefined) { - wfsCache.current.clear(); - fetchMetadata.current.clear(); + wfsCacheRef.clear(); + fetchMetadataRef.clear(); mapViewer.dataSources.remove(mapViewer.dataSources.getByName(`${labeling?.dataSourcePrefix}${wfsDataSource.name}`)[0]); mapViewer.dataSources.remove(wfsDataSource, true); if (dataLayerId !== undefined) { diff --git a/packages/react-components/src/components/cesium-map/legend/legend-item.tsx b/packages/react-components/src/components/cesium-map/legend/legend-item.tsx index 68df4fcb..099b8551 100644 --- a/packages/react-components/src/components/cesium-map/legend/legend-item.tsx +++ b/packages/react-components/src/components/cesium-map/legend/legend-item.tsx @@ -23,11 +23,6 @@ export const LegendItem: React.FC = ({ legend: { legendImg, leg window.open(legendImg, '_blank'); }, [legendImg]); - const handleLegendDocOpen = useCallback(() => { - // Open doc in a new tab. - window.open(legendDoc, '_blank'); - }, [legendDoc]); - const renderLayerName = useCallback(() => { const MAX_LAYER_NAME_LENGTH = 15; diff --git a/packages/react-components/src/components/cesium-map/legend/legend-list.tsx b/packages/react-components/src/components/cesium-map/legend/legend-list.tsx index f742121f..2ce15995 100644 --- a/packages/react-components/src/components/cesium-map/legend/legend-list.tsx +++ b/packages/react-components/src/components/cesium-map/legend/legend-list.tsx @@ -26,7 +26,6 @@ export const LegendList: React.FC = ({ legends, actionsTexts: { if (!legends.length) { return handleNoLegends(); } - return legends.map((legend, i) => { return ; }); diff --git a/packages/react-components/src/components/cesium-map/map.stories.tsx b/packages/react-components/src/components/cesium-map/map.stories.tsx index 53af2f63..940897c6 100644 --- a/packages/react-components/src/components/cesium-map/map.stories.tsx +++ b/packages/react-components/src/components/cesium-map/map.stories.tsx @@ -1,5 +1,5 @@ import { Feature } from 'geojson'; -import React, { useState, useEffect } from 'react'; +import { useState, useEffect } from 'react'; import type { StoryFn, Meta } from '@storybook/react'; import { ThemeProvider } from '@map-colonies/react-core'; import { getValue } from '../utils/config'; diff --git a/packages/react-components/src/components/cesium-map/map.tsx b/packages/react-components/src/components/cesium-map/map.tsx index d7c6545c..d88ae767 100644 --- a/packages/react-components/src/components/cesium-map/map.tsx +++ b/packages/react-components/src/components/cesium-map/map.tsx @@ -227,6 +227,17 @@ export const CesiumMap: React.FC = (props) => { setMapViewRef(viewer as CesiumViewer); }, []); + useEffect(() => { + return () => { + if (mapViewRef && !mapViewRef.isDestroyed()) { + const canvas = mapViewRef.canvas; + mapViewRef.destroy(); + const gl = canvas.getContext('webgl2') ?? canvas.getContext('webgl'); + gl?.getExtension('WEBGL_lose_context')?.loseContext(); + } + }; + }, [mapViewRef]); + const userExtend = (props as ViewerProps).extend; const mergedExtend = userExtend ? (Array.isArray(userExtend) ? [...userExtend, onViewerReady] : [userExtend, onViewerReady]) : onViewerReady; @@ -428,7 +439,7 @@ export const CesiumMap: React.FC = (props) => { }; if (mapViewRef) { - mapViewRef.camera.moveEnd.addEventListener(() => { + const moveEndHandler = () => { if (mapViewRef.scene.mode !== SceneMode.MORPHING) { const camera = mapViewRef.camera; @@ -442,16 +453,23 @@ export const CesiumMap: React.FC = (props) => { }; cameraStateRef.current = store; } - }); + }; + + const removeMoveEndListener = mapViewRef.camera.moveEnd.addEventListener(moveEndHandler); + let removeTileLoadProgressListener: (() => void) | undefined; + let dataLayerUpdatedHandler: ((meta: any) => void) | undefined; + if (showLoadingProgress) { - mapViewRef.scene.globe.tileLoadProgressEvent.addEventListener(function () { + const tileLoadProgressHandler = () => { if (mapViewRef.scene.globe.tilesLoaded) { setIsLoadingTiles(false); } else { setIsLoadingTiles(true); } - }); - mapViewRef.layersManager?.addDataLayerUpdatedListener(() => { + }; + removeTileLoadProgressListener = mapViewRef.scene.globe.tileLoadProgressEvent.addEventListener(tileLoadProgressHandler); + + dataLayerUpdatedHandler = () => { let loading = false; mapViewRef.layersManager?.dataLayerList.forEach((dataLayer) => { if ( @@ -465,8 +483,21 @@ export const CesiumMap: React.FC = (props) => { } }); setIsLoadingDataLayer(loading); - }); + }; + mapViewRef.layersManager?.addDataLayerUpdatedListener(dataLayerUpdatedHandler); } + + return () => { + try { + removeMoveEndListener(); + removeTileLoadProgressListener?.(); + if (dataLayerUpdatedHandler) { + mapViewRef.layersManager?.removeDataLayerUpdatedListener(dataLayerUpdatedHandler); + } + } catch (e) { + console.error('Cesium map listeners cleanup failed:', e); + } + }; } }, [mapViewRef]); @@ -532,6 +563,10 @@ export const CesiumMap: React.FC = (props) => { }; const bindCustomToolsToViewer = useCallback((): JSX.Element | undefined => { + const viewerContainer = mapViewRef?.container?.querySelector('.cesium-viewer'); + if (!viewerContainer) { + return undefined; + } return ( mapViewRef && createPortal( @@ -545,12 +580,16 @@ export const CesiumMap: React.FC = (props) => { {showZoomButtons && } , - document.querySelector('.cesium-viewer') as Element + viewerContainer ) ); - }, [mapViewRef, locale, projection, showMousePosition, showScale, isLoadingProgress]); + }, [mapViewRef, locale, projection, showMousePosition, showScale, isLoadingProgress, showCompass, showLoadingProgress, showZoomButtons, showZoomLevel]); const bindToolsToToolbar = useCallback((): JSX.Element | undefined => { + const toolbarContainer = mapViewRef?.container?.querySelector('.cesium-viewer-toolbar'); + if (!toolbarContainer) { + return undefined; + } return ( mapViewRef && createPortal( @@ -560,12 +599,16 @@ export const CesiumMap: React.FC = (props) => { {props.showDebuggerTool && } , - document.querySelector('.cesium-viewer-toolbar') as Element + toolbarContainer ) ); - }, [mapViewRef, locale, baseMaps, terrains]); + }, [mapViewRef, locale, baseMaps, terrains, props.geocoderPanel, props.showDebuggerTool]); const bindInspectorsToWidgets = useCallback((): JSX.Element | undefined => { + const widgetContainer = mapViewRef?.container?.querySelector('.cesium-widget'); + if (!widgetContainer) { + return undefined; + } return ( mapViewRef && createPortal( @@ -573,10 +616,10 @@ export const CesiumMap: React.FC = (props) => { {showActiveLayersTool && } {viewState?.showCesiumInspector && } , - document.querySelector('.cesium-widget') as Element + widgetContainer ) ); - }, [mapViewRef, locale, viewState?.showCesiumInspector]); + }, [mapViewRef, locale, viewState?.showCesiumInspector, showActiveLayersTool]); return ( diff --git a/packages/react-components/src/components/cesium-map/terrain-providers/terrain-provider-heights-tool.stories.tsx b/packages/react-components/src/components/cesium-map/terrain-providers/terrain-provider-heights-tool.stories.tsx index 2f52ef09..9225a58b 100644 --- a/packages/react-components/src/components/cesium-map/terrain-providers/terrain-provider-heights-tool.stories.tsx +++ b/packages/react-components/src/components/cesium-map/terrain-providers/terrain-provider-heights-tool.stories.tsx @@ -72,13 +72,6 @@ const TerrainProviderSelector: React.FC = ({ terr return (