diff --git a/packages/geoview-core/src/api/event-processors/event-processor-children/legend-event-processor.ts b/packages/geoview-core/src/api/event-processors/event-processor-children/legend-event-processor.ts index 59f456705ef..e09dbe98e4b 100644 --- a/packages/geoview-core/src/api/event-processors/event-processor-children/legend-event-processor.ts +++ b/packages/geoview-core/src/api/event-processors/event-processor-children/legend-event-processor.ts @@ -353,12 +353,12 @@ export class LegendEventProcessor extends AbstractEventProcessor { // Update the legend layers with the updated array, triggering the subscribe // Reorder the array so legend tab is in synch - const sortedLayers = layers.sort((a, b) => - MapEventProcessor.getMapIndexFromOrderedLayerInfo(mapId, a.layerPath) > - MapEventProcessor.getMapIndexFromOrderedLayerInfo(mapId, b.layerPath) - ? 1 - : -1 + const sortedLayers = layers.sort( + (a, b) => + MapEventProcessor.getMapIndexFromOrderedLayerInfo(mapId, a.layerPath) - + MapEventProcessor.getMapIndexFromOrderedLayerInfo(mapId, b.layerPath) ); + this.sortLegendLayersChildren(mapId, sortedLayers); this.getLayerState(mapId).setterActions.setLegendLayers(sortedLayers); } @@ -397,7 +397,7 @@ export class LegendEventProcessor extends AbstractEventProcessor { foundLayer = layer; } - if (layerPath?.startsWith(layer.layerPath) && layer.children?.length > 0) { + if (layerPath.startsWith(`${layer.layerPath}/`) && layer.children?.length > 0) { const result: TypeLegendLayer | undefined = LegendEventProcessor.findLayerByPath(layer.children, layerPath); if (result) { foundLayer = result; @@ -742,4 +742,21 @@ export class LegendEventProcessor extends AbstractEventProcessor { return matchingBreak ? matchingBreak.visible : classBreakStyle.info[classBreakStyle.info.length - 1].visible; }); } + + /** + * Sorts legend layers children recursively in given legend layers list. + * @param {string} mapId - The ID of the map. + * @param {TypeLegendLayer[]} legendLayerList - The list to sort. + */ + static sortLegendLayersChildren = (mapId: string, legendLayerList: TypeLegendLayer[]): void => { + legendLayerList.forEach((legendLayer) => { + if (legendLayer.children.length) + legendLayer.children.sort( + (a, b) => + MapEventProcessor.getMapIndexFromOrderedLayerInfo(mapId, a.layerPath) - + MapEventProcessor.getMapIndexFromOrderedLayerInfo(mapId, b.layerPath) + ); + this.sortLegendLayersChildren(mapId, legendLayer.children); + }); + }; } diff --git a/packages/geoview-core/src/api/event-processors/event-processor-children/map-event-processor.ts b/packages/geoview-core/src/api/event-processors/event-processor-children/map-event-processor.ts index dc3ca4f1ed0..66518e0c22f 100644 --- a/packages/geoview-core/src/api/event-processors/event-processor-children/map-event-processor.ts +++ b/packages/geoview-core/src/api/event-processors/event-processor-children/map-event-processor.ts @@ -558,6 +558,23 @@ export class MapEventProcessor extends AbstractEventProcessor { return this.getMapStateProtected(mapId).orderedLayerInfo.find((orderedLayerInfo) => orderedLayerInfo.layerPath === layerPath); } + /** + * Gets the ordered layer info for one layer and its children. + * @param {string} mapId - The map id. + * @param {string} layerPath - The path of the layer to get. + * @param {TypeOrderedLayerInfo[]} orderedLayerInfo - The array of ordered layer info to search, default is current ordered layer info. + * @returns {TypeOrderedLayerInfo[] | undefined} The ordered layer info of the layer and its children. + */ + static getMapLayerAndChildrenOrderedInfo( + mapId: string, + layerPath: string, + orderedLayerInfo: TypeOrderedLayerInfo[] = this.getMapStateProtected(mapId).orderedLayerInfo + ): TypeOrderedLayerInfo[] { + return orderedLayerInfo.filter( + (info: TypeOrderedLayerInfo) => info.layerPath.startsWith(`${layerPath}/`) || info.layerPath === layerPath + ); + } + static getMapIndexFromOrderedLayerInfo(mapId: string, layerPath: string): number { // Get index of a layer const info = this.getMapStateProtected(mapId).orderedLayerInfo; @@ -749,8 +766,9 @@ export class MapEventProcessor extends AbstractEventProcessor { const layerPath = (geoviewLayerConfig as TypeGeoviewLayerConfig).geoviewLayerId ? `${(geoviewLayerConfig as TypeGeoviewLayerConfig).geoviewLayerId}/${(geoviewLayerConfig as TypeGeoviewLayerConfig).geoviewLayerId}` : (geoviewLayerConfig as TypeLayerEntryConfig).layerPath; - const index = this.getMapIndexFromOrderedLayerInfo(mapId, layerPathToReplace || layerPath); - const replacedLayers = orderedLayerInfo.filter((layerInfo) => layerInfo.layerPath.startsWith(layerPathToReplace || layerPath)); + const pathToSearch = layerPathToReplace || layerPath; + const index = this.getMapIndexFromOrderedLayerInfo(mapId, pathToSearch); + const replacedLayers = this.getMapLayerAndChildrenOrderedInfo(mapId, pathToSearch); const newOrderedLayerInfo = LayerApi.generateArrayOfLayerOrderInfo(geoviewLayerConfig); orderedLayerInfo.splice(index, replacedLayers.length, ...newOrderedLayerInfo); @@ -803,7 +821,9 @@ export class MapEventProcessor extends AbstractEventProcessor { */ static removeOrderedLayerInfo(mapId: string, layerPath: string): void { const { orderedLayerInfo } = this.getMapStateProtected(mapId); - const newOrderedLayerInfo = orderedLayerInfo.filter((layerInfo) => !layerInfo.layerPath.startsWith(layerPath)); + const newOrderedLayerInfo = orderedLayerInfo.filter( + (layerInfo) => !layerInfo.layerPath.startsWith(`${layerPath}/`) || !(layerInfo.layerPath === layerPath) + ); // Redirect this.setMapOrderedLayerInfo(mapId, newOrderedLayerInfo); @@ -1087,7 +1107,8 @@ export class MapEventProcessor extends AbstractEventProcessor { const listOfLayerEntryConfig: TypeLayerEntryConfig[] = []; if (layerEntryConfig!.entryType === 'group') { const sublayerPaths = MapEventProcessor.getMapLayerOrder(mapId).filter( - (entryLayerPath) => entryLayerPath.startsWith(layerPath) && entryLayerPath.split('/').length === layerPath.split('/').length + 1 + (entryLayerPath) => + entryLayerPath.startsWith(`${layerPath}/`) && entryLayerPath.split('/').length === layerPath.split('/').length + 1 ); sublayerPaths.forEach((sublayerPath) => listOfLayerEntryConfig.push(MapEventProcessor.createLayerEntryConfig(mapId, sublayerPath))); } @@ -1134,7 +1155,7 @@ export class MapEventProcessor extends AbstractEventProcessor { // Check for sublayers const sublayerPaths = MapEventProcessor.getMapLayerOrder(mapId).filter( // We only want the immediate child layers, group sublayers will handle their own sublayers - (entryLayerPath) => entryLayerPath.startsWith(layerPath) && entryLayerPath.split('/').length === layerPath.split('/').length + 1 + (entryLayerPath) => entryLayerPath.startsWith(`${layerPath}/`) && entryLayerPath.split('/').length === layerPath.split('/').length + 1 ); // Build list of sublayer entry configs diff --git a/packages/geoview-core/src/core/components/layers/left-panel/layers-list.tsx b/packages/geoview-core/src/core/components/layers/left-panel/layers-list.tsx index b12040b775b..e1e7d485cfb 100644 --- a/packages/geoview-core/src/core/components/layers/left-panel/layers-list.tsx +++ b/packages/geoview-core/src/core/components/layers/left-panel/layers-list.tsx @@ -2,7 +2,7 @@ import { useTheme } from '@mui/material/styles'; import { SingleLayer } from './single-layer'; import { getSxClasses } from './left-panel-styles'; import { Box } from '@/ui'; -import { useGeoViewMapId, useMapStoreActions } from '@/core/stores'; +import { useGeoViewMapId, useLayerStoreActions, useMapStoreActions } from '@/core/stores'; import { logger } from '@/core/utils/logger'; import { TypeLegendLayer } from '@/core/components/layers/types'; import { TABS } from '@/core/utils/constant'; @@ -23,10 +23,12 @@ export function LayersList({ layersList, showLayerDetailsPanel, isLayoutEnlarged const mapId = useGeoViewMapId(); const { getIndexFromOrderedLayerInfo } = useMapStoreActions(); + const { sortLegendLayersChildren } = useLayerStoreActions(); const sortedLayers = layersList.sort((a, b) => getIndexFromOrderedLayerInfo(a.layerPath) > getIndexFromOrderedLayerInfo(b.layerPath) ? 1 : -1 ); + sortLegendLayersChildren(sortedLayers); const textToSlug = (text: string): string => { return text diff --git a/packages/geoview-core/src/core/stores/state-api.ts b/packages/geoview-core/src/core/stores/state-api.ts index 0ced1c749e9..c5eb02aeebc 100644 --- a/packages/geoview-core/src/core/stores/state-api.ts +++ b/packages/geoview-core/src/core/stores/state-api.ts @@ -114,7 +114,7 @@ export class StateApi { let startingIndex = -1; for (let i = 0; i < orderedLayers.length; i++) if (orderedLayers[i].layerPath === layerPath) startingIndex = i; const layerInfo = orderedLayers[startingIndex]; - const movedLayers = orderedLayers.filter((layer) => layer.layerPath.startsWith(layerPath)); + const movedLayers = MapEventProcessor.getMapLayerAndChildrenOrderedInfo(mapId, layerPath, orderedLayers); orderedLayers.splice(startingIndex, movedLayers.length); let nextIndex = startingIndex; const pathLength = layerInfo.layerPath.split('/').length; diff --git a/packages/geoview-core/src/core/stores/store-interface-and-intial-values/layer-state.ts b/packages/geoview-core/src/core/stores/store-interface-and-intial-values/layer-state.ts index 1810915c023..e64addf4f2b 100644 --- a/packages/geoview-core/src/core/stores/store-interface-and-intial-values/layer-state.ts +++ b/packages/geoview-core/src/core/stores/store-interface-and-intial-values/layer-state.ts @@ -52,6 +52,7 @@ export interface ILayerState { setLayerDeleteInProgress: (newVal: boolean) => void; setLayerOpacity: (layerPath: string, opacity: number) => void; setSelectedLayerPath: (layerPath: string) => void; + sortLegendLayersChildren: (legendLayerList: TypeLegendLayer[]) => void; toggleItemVisibility: (layerPath: string, item: TypeLegendItem) => void; zoomToLayerExtent: (layerPath: string) => Promise; setSelectedLayerSortingArrowId: (layerId: string) => void; @@ -236,6 +237,14 @@ export function initializeLayerState(set: TypeSetStore, get: TypeGetStore): ILay LegendEventProcessor.setSelectedLayersTabLayer(get().mapId, layerPath); }, + /** + * Sorts legend layers children recursively in given legend layers list. + * @param {TypeLegendLayer[]} legendLayerList - The list to sort. + */ + sortLegendLayersChildren: (legendLayerList: TypeLegendLayer[]): void => { + LegendEventProcessor.sortLegendLayersChildren(get().mapId, legendLayerList); + }, + /** * Toggle visibility of an item. * @param {string} layerPath - The layer path of the layer to change. diff --git a/packages/geoview-core/src/geo/layer/layer.ts b/packages/geoview-core/src/geo/layer/layer.ts index 418f05da3cd..403649deb85 100644 --- a/packages/geoview-core/src/geo/layer/layer.ts +++ b/packages/geoview-core/src/geo/layer/layer.ts @@ -1004,14 +1004,11 @@ export class LayerApi { MapEventProcessor.replaceOrderedLayerInfo(this.getMapId(), layerConfig, parentLayerPath); } else if (layerConfig.parentLayerConfig) { // Here the map index of a sub layer path hasn't been set and there's a parent layer config for the current layer config - // Get the map index of the parent layer path const parentLayerIndex = MapEventProcessor.getMapIndexFromOrderedLayerInfo(this.getMapId(), parentLayerPath); - // Get the number of child layers - const numberOfLayers = MapEventProcessor.getMapOrderedLayerInfo(this.getMapId()).filter((layerInfo) => - layerInfo.layerPath.startsWith(parentLayerPath) - ).length; + // Get the number of layers + const numberOfLayers = MapEventProcessor.getMapLayerAndChildrenOrderedInfo(this.getMapId(), parentLayerPath).length; // If the map index of the parent has been set if (parentLayerIndex !== -1) { @@ -1201,7 +1198,7 @@ export class LayerApi { // Remove layer info from registered layers this.getLayerEntryConfigIds().forEach((registeredLayerPath) => { - if (registeredLayerPath.startsWith(layerPath)) { + if (registeredLayerPath.startsWith(`${layerPath}/`) || registeredLayerPath === layerPath) { // Remove ol layer if (this.getOLLayer(registeredLayerPath)) this.mapViewer.map.removeLayer(this.getOLLayer(registeredLayerPath) as BaseLayer); // Unregister layer @@ -1280,7 +1277,10 @@ export class LayerApi { // Trying to get the layer associated with the layer path, can be undefined because the layer might be in error const theLayer = this.getGeoviewLayer(registeredLayerPath); if (theLayer) { - if (!registeredLayerPath.startsWith(layerPath) && !layerEntryIsGroupLayer(this.#layerEntryConfigs[registeredLayerPath])) { + if ( + !(registeredLayerPath.startsWith(`${layerPath}/`) || registeredLayerPath === layerPath) && + !layerEntryIsGroupLayer(this.#layerEntryConfigs[registeredLayerPath]) + ) { const otherOpacity = theLayer.getOpacity(); theLayer.setOpacity((otherOpacity || 1) * 0.25); } else this.getOLLayer(registeredLayerPath)!.setZIndex(999); @@ -1313,7 +1313,10 @@ export class LayerApi { // Trying to get the layer associated with the layer path, can be undefined because the layer might be in error const theLayer = this.getGeoviewLayer(registeredLayerPath); if (theLayer) { - if (!registeredLayerPath.startsWith(layerPath) && !layerEntryIsGroupLayer(this.#layerEntryConfigs[registeredLayerPath])) { + if ( + !(registeredLayerPath.startsWith(`${layerPath}/`) || registeredLayerPath === layerPath) && + !layerEntryIsGroupLayer(this.#layerEntryConfigs[registeredLayerPath]) + ) { const otherOpacity = theLayer.getOpacity(); theLayer.setOpacity(otherOpacity ? otherOpacity * 4 : 1); } else theLayer.setOpacity(originalOpacity || 1); @@ -1348,7 +1351,9 @@ export class LayerApi { layerIds.forEach((layerId) => { // Get sublayerpaths and layerpaths from layer IDs. - const subLayerPaths = Object.keys(this.#layerEntryConfigs).filter((layerPath) => layerPath.startsWith(layerId)); + const subLayerPaths = Object.keys(this.#layerEntryConfigs).filter( + (layerPath) => layerPath.startsWith(`${layerId}/`) || layerPath === layerId + ); if (subLayerPaths.length) { // Get max extents from all selected layers. @@ -1457,7 +1462,7 @@ export class LayerApi { const layerVisibility = MapEventProcessor.getMapVisibilityFromOrderedLayerInfo(this.getMapId(), layerPath); // Determine the outcome of the new visibility based on parameters const newVisibility = newValue !== undefined ? newValue : !layerVisibility; - const layerInfos = curOrderedLayerInfo.filter((info: TypeOrderedLayerInfo) => info.layerPath.startsWith(layerPath)); + const layerInfos = MapEventProcessor.getMapLayerAndChildrenOrderedInfo(this.getMapId(), layerPath, curOrderedLayerInfo); layerInfos.forEach((layerInfo: TypeOrderedLayerInfo) => { if (layerInfo) { @@ -1491,7 +1496,7 @@ export class LayerApi { } const children = curOrderedLayerInfo.filter( // eslint-disable-next-line no-loop-func - (info: TypeOrderedLayerInfo) => info.layerPath.startsWith(parentLayerPath) && info.layerPath !== parentLayerPath + (info: TypeOrderedLayerInfo) => info.layerPath.startsWith(`${parentLayerPath}/`) && info.layerPath !== parentLayerPath ); if (!children.some((child: TypeOrderedLayerInfo) => child.visible === true)) { this.setOrToggleLayerVisibility(parentLayerPath, false);