diff --git a/packages/geoview-core/public/configs/package-swiper-config-swiper.json b/packages/geoview-core/public/configs/package-swiper-config-swiper.json index efa3163e56a..210358cb082 100644 --- a/packages/geoview-core/public/configs/package-swiper-config-swiper.json +++ b/packages/geoview-core/public/configs/package-swiper-config-swiper.json @@ -1,6 +1,6 @@ { "orientation": "vertical", "keyboardOffset": 10, - "layers": ["esriFeatureLYR4"], + "layers": ["esriFeatureLYR4/0"], "suportedLanguages": ["en", "fr"] } \ No newline at end of file diff --git a/packages/geoview-core/public/configs/package-swiper2-config-swiper.json b/packages/geoview-core/public/configs/package-swiper2-config-swiper.json index 42402264c4a..c3f4ace7e84 100644 --- a/packages/geoview-core/public/configs/package-swiper2-config-swiper.json +++ b/packages/geoview-core/public/configs/package-swiper2-config-swiper.json @@ -1,6 +1,6 @@ { "orientation": "horizontal", "keyboardOffset": 10, - "layers": ["esriFeatureLYR4-map2", "esriDynamicLYR3-map2"], + "layers": ["esriFeatureLYR4-map2/2", "esriDynamicLYR3-map2/0"], "suportedLanguages": ["en", "fr"] } \ No newline at end of file diff --git a/packages/geoview-core/public/configs/package-swiper3-config-swiper.json b/packages/geoview-core/public/configs/package-swiper3-config-swiper.json index 1af7ac7c116..220d9b4ca23 100644 --- a/packages/geoview-core/public/configs/package-swiper3-config-swiper.json +++ b/packages/geoview-core/public/configs/package-swiper3-config-swiper.json @@ -1,6 +1,6 @@ { "orientation": "vertical", "keyboardOffset": 10, - "layers": ["swipe0", "swipe1", "swipe4", "swipe5", "swipe6", "rcs.ccc75c12-5acc-4a6a-959f-ef6f621147b9.en", "swipe7"], + "layers": ["swipe0/toner", "swipe1/msi-94-or-more", "swipe4/polygons.json", "swipe5/ec-msc:CURRENT_CONDITIONS", "swipe6/lakes", "rcs.ccc75c12-5acc-4a6a-959f-ef6f621147b9.en/0"], "suportedLanguages": ["en", "fr"] } diff --git a/packages/geoview-core/src/api/event-processors/abstract-event-processor.ts b/packages/geoview-core/src/api/event-processors/abstract-event-processor.ts index b229b908dd3..23511af8541 100644 --- a/packages/geoview-core/src/api/event-processors/abstract-event-processor.ts +++ b/packages/geoview-core/src/api/event-processors/abstract-event-processor.ts @@ -48,6 +48,7 @@ export abstract class AbstractEventProcessor { // eslint-disable-next-line @typescript-eslint/no-unused-vars protected onInitialize(store: GeoviewStoreType): Array<() => void> | void { + // Here, `store` is unused, but used in inherited classes, so the eslint-disable should be kept // This method should be overriden to initialize and return a list of subscribtions so that they can be destroyed later return undefined; } diff --git a/packages/geoview-core/src/api/event-processors/event-processor-children/feature-info-event-processor.ts b/packages/geoview-core/src/api/event-processors/event-processor-children/feature-info-event-processor.ts index ef9cc9433bb..7035c02f621 100644 --- a/packages/geoview-core/src/api/event-processors/event-processor-children/feature-info-event-processor.ts +++ b/packages/geoview-core/src/api/event-processors/event-processor-children/feature-info-event-processor.ts @@ -57,7 +57,7 @@ export class FeatureInfoEventProcessor extends AbstractEventProcessor { FeatureInfoEventProcessor.deleteFeatureAllInfo(store.getState().mapId, layerPath); // Log - logger.logDebug('Removed Feature Info in stores for layer path:', layerPath); + logger.logInfo('Removed Feature Info in stores for layer path:', layerPath); } }); } diff --git a/packages/geoview-core/src/api/event-processors/event-processor-children/geochart-event-processor.ts b/packages/geoview-core/src/api/event-processors/event-processor-children/geochart-event-processor.ts index 846c0193e8a..07f5cdcc949 100644 --- a/packages/geoview-core/src/api/event-processors/event-processor-children/geochart-event-processor.ts +++ b/packages/geoview-core/src/api/event-processors/event-processor-children/geochart-event-processor.ts @@ -47,9 +47,6 @@ export class GeochartEventProcessor extends AbstractEventProcessor { if (prevOrderedLayerPaths.includes(layerPath) && !curOrderedLayerPaths.includes(layerPath)) { // Remove it GeochartEventProcessor.removeGeochartChart(store.getState().mapId, layerPath); - - // Log - logger.logDebug('Removed GeoChart configs for layer path:', layerPath); } }); } @@ -95,18 +92,23 @@ export class GeochartEventProcessor extends AbstractEventProcessor { const chartData: GeoChartStoreByLayerPath = {}; // Loop on the charts + const layerPaths: string[] = []; charts.forEach((chartInfo) => { // For each layer path chartInfo.layers.forEach((layer) => { // Get the layer path const layerPath = layer.layerId; chartData[layerPath] = chartInfo; + layerPaths.push(layerPath); }); }); // set store charts config this.getGeochartState(mapId)?.actions.setGeochartCharts(chartData); + // Log + logger.logInfo('Added GeoChart configs for layer paths:', layerPaths); + // TODO: Also update the layer array in other store state to inform the later has a geochart attached to it (when code is done over there)? } @@ -128,6 +130,9 @@ export class GeochartEventProcessor extends AbstractEventProcessor { // Update the layer data array in the store this.getGeochartState(mapId)!.actions.setGeochartCharts({ ...this.getGeochartState(mapId)?.geochartChartsConfig, ...toAdd }); + // Log + logger.logInfo('Added GeoChart configs for layer path:', layerPath); + // TODO: Also update the layer array in other store state to inform the later has a geochart attached to it (when code is done over there)? } @@ -153,6 +158,9 @@ export class GeochartEventProcessor extends AbstractEventProcessor { // Update the layer data array in the store this.getGeochartState(mapId)!.actions.setGeochartCharts({ ...chartConfigs }); + // Log + logger.logInfo('Removed GeoChart configs for layer path:', layerPath); + // TODO: Also update the layer array in other store state to inform the later has a geochart attached to it (when code is done over there)? } } diff --git a/packages/geoview-core/src/api/event-processors/event-processor-children/swiper-event-processor.ts b/packages/geoview-core/src/api/event-processors/event-processor-children/swiper-event-processor.ts new file mode 100644 index 00000000000..4753764dcd4 --- /dev/null +++ b/packages/geoview-core/src/api/event-processors/event-processor-children/swiper-event-processor.ts @@ -0,0 +1,169 @@ +import { GeoviewStoreType } from '@/core/stores'; +import { ISwiperState } from '@/core/stores/store-interface-and-intial-values/swiper-state'; +import { logger } from '@/core/utils/logger'; + +import { AbstractEventProcessor } from '../abstract-event-processor'; + +/** + * Event processor focusing on interacting with the swiper state in the store. + */ +export class SwiperEventProcessor extends AbstractEventProcessor { + // ********************************************************** + // Static functions for Typescript files to access store actions + // ********************************************************** + //! Typescript MUST always use the defined store actions below to modify store - NEVER use setState! + //! Some action does state modifications AND map actions. + //! ALWAYS use map event processor when an action modify store and IS NOT trap by map state event handler + + /** + * Overrides initialization of the Swiper Event Processor + * @param {GeoviewStoreType} store The store associated with the Swiper Event Processor + * @returns An array of the subscriptions callbacks which were created + */ + protected onInitialize(store: GeoviewStoreType): Array<() => void> | void { + // Checks for udpated layers in layer order + const unsubLayerRemoved = store.subscribe( + (state) => state.mapState.orderedLayerInfo, + (cur, prev) => { + // Log + logger.logTraceCoreStoreSubscription('SWIPER EVENT PROCESSOR - orderedLayerInfo', cur); + + // Read the layer paths of each layer info + const curOrderedLayerPaths = cur.map((layerInfo) => layerInfo.layerPath); + const prevOrderedLayerPaths = prev.map((layerInfo) => layerInfo.layerPath); + + // Get all the layer paths to check in a distinct array for looping purposes + const layerPathsToCheck = [...store.getState().swiperState.layerPaths]; + + // For each layer paths the swiper is using + layerPathsToCheck.forEach((layerPath) => { + // If it was in the layerdata array and is not anymore + if (prevOrderedLayerPaths.includes(layerPath) && !curOrderedLayerPaths.includes(layerPath)) { + // Remove it + SwiperEventProcessor.removeLayerPath(store.getState().mapId, layerPath); + } + }); + } + ); + + return [unsubLayerRemoved]; + } + + /** + * Shortcut to get the Swiper state for a given map id + * @param {string} mapId The mapId + * @returns {ISwiperState | undefined} The Swiper state. Forcing the return to also be 'undefined', because + * there will be no swiperState if the Swiper plugin isn't active. + * This helps the developers making sure the existence is checked. + */ + protected static getSwiperState(mapId: string): ISwiperState | undefined { + // Return the swiper state when it exists + return super.getState(mapId).swiperState; + } + + /** + * Sets the layer paths on which the swiper should be activated. + * + * @param {string} mapId the map id + * @param {string[]} layerPaths The array of layer paths + */ + static setLayerPaths(mapId: string, layerPaths: string[]): void { + // set store layer paths + this.getSwiperState(mapId)?.actions.setLayerPaths(layerPaths); + + // Log + logger.logInfo('Added Swiper functionality for layer paths:', layerPaths); + + // TODO: Also update the layer array in other store state to inform the later has a swiper attached to it? + } + + /** + * Adds a swipe functionality to the specified map id and layer path + * @param {string} mapId The map ID + * @param {string} layerPath The layer path + */ + static addLayerPath(mapId: string, layerPath: string): void { + // The processor needs an initialized layer paths store which is only initialized if the Swiper Plugin exists. + // Therefore, we validate its existence first. + if (!this.getSwiperState(mapId)) return; + if (!this.getSwiperState(mapId)?.layerPaths) return; + + // If not already added + if (!this.getSwiperState(mapId)!.layerPaths.includes(layerPath)) { + // Add in the array + const updatedArray = [...this.getSwiperState(mapId)!.layerPaths]; + updatedArray.push(layerPath); + + // Update the layer data array in the store + this.getSwiperState(mapId)!.actions.setLayerPaths(updatedArray); + + // Log + logger.logInfo('Added Swiper functionality for layer path:', layerPath); + + // TODO: Also update the layer array in other store state to inform the later has a swiper attached to it? + } else { + // Log + logger.logInfo('Swiper functionality already active for layer path:', layerPath); + } + } + + /** + * Removes a swipe functionality for the specified map id and layer path + * @param {string} mapId The map ID + * @param {string} layerPath The layer path + */ + static removeLayerPath(mapId: string, layerPath: string): void { + // The processor needs an initialized layer paths store which is only initialized if the Swiper Plugin exists. + // Therefore, we validate its existence first. + if (!this.getSwiperState(mapId)) return; + if (!this.getSwiperState(mapId)?.layerPaths) return; + + // Find the index with the layer path + const layerIndex = this.getSwiperState(mapId)!.layerPaths.findIndex((layer) => layer === layerPath); + + // Config to remove + if (layerIndex !== undefined && layerIndex >= 0) { + // Remove from the array + const updatedArray = [...this.getSwiperState(mapId)!.layerPaths]; + updatedArray.splice(layerIndex, 1); + + // Update the layer data array in the store + this.getSwiperState(mapId)!.actions.setLayerPaths(updatedArray); + + // Log + logger.logInfo('Removed Swiper functionality for layer path:', layerPath); + + // TODO: Also update the layer array in other store state to inform the later has a swiper attached to it? + } + } + + /** + * Removes the swipe functionality for all layer paths + * @param {string} mapId The map ID + */ + static removeAll(mapId: string) { + // The processor needs an initialized layer paths store which is only initialized if the Swiper Plugin exists. + // Therefore, we validate its existence first. + if (!this.getSwiperState(mapId)) return; + if (!this.getSwiperState(mapId)?.layerPaths) return; + + // Get all layer paths + const { layerPaths } = this.getSwiperState(mapId)!; + + // Update the layer data array in the store + this.getSwiperState(mapId)!.actions.setLayerPaths([]); + + // Log + logger.logInfo('Removed Swiper functionality for all layer paths', layerPaths); + + // TODO: Also update the layer array in other store state to inform the later has a swiper attached to it? + } + + // #endregion + + // ********************************************************** + // Static functions for Store Map State to action on API + // ********************************************************** + //! NEVER add a store action who does set state AND map action at a same time. + //! Review the action in store state to make sure +} diff --git a/packages/geoview-core/src/api/event-processors/index.ts b/packages/geoview-core/src/api/event-processors/index.ts index 3cfed4f4ef4..92c7a8ca53a 100644 --- a/packages/geoview-core/src/api/event-processors/index.ts +++ b/packages/geoview-core/src/api/event-processors/index.ts @@ -6,6 +6,7 @@ import { MapEventProcessor } from '@/api/event-processors/event-processor-childr import { TimeSliderEventProcessor } from '@/api/event-processors/event-processor-children/time-slider-event-processor'; import { GeochartEventProcessor } from '@/api/event-processors/event-processor-children/geochart-event-processor'; import { DataTableProcessor } from '@/api/event-processors/event-processor-children/data-table-processor'; +import { SwiperEventProcessor } from './event-processor-children/swiper-event-processor'; // core const appEventProcessor = new AppEventProcessor(); @@ -17,6 +18,7 @@ const dataTableProcessor = new DataTableProcessor(); // packages const timeSliderEventProcessor = new TimeSliderEventProcessor(); const geochartEventProcessor = new GeochartEventProcessor(); +const swiperEventProcessor = new SwiperEventProcessor(); export function initializeEventProcessors(store: GeoviewStoreType) { // core stores @@ -30,6 +32,7 @@ export function initializeEventProcessors(store: GeoviewStoreType) { // TODO: Change this check for something more generic that checks in appBar too if (store.getState().mapConfig!.footerBar?.tabs.core.includes('time-slider')) timeSliderEventProcessor.initialize(store); if (store.getState().mapConfig!.footerBar?.tabs.core.includes('geochart')) geochartEventProcessor.initialize(store); + if (store.getState().mapConfig!.corePackages?.includes('swiper')) swiperEventProcessor.initialize(store); } export function destroyEventProcessors(store: GeoviewStoreType) { @@ -44,4 +47,5 @@ export function destroyEventProcessors(store: GeoviewStoreType) { // TODO: Change this check for something more generic that checks in appBar too if (store.getState().mapConfig!.footerBar?.tabs.core.includes('time-slider')) timeSliderEventProcessor.destroy(); if (store.getState().mapConfig!.footerBar?.tabs.core.includes('geochart')) geochartEventProcessor.destroy(); + if (store.getState().mapConfig!.corePackages?.includes('swiper')) swiperEventProcessor.destroy(); } diff --git a/packages/geoview-core/src/api/plugin/map-plugin.ts b/packages/geoview-core/src/api/plugin/map-plugin.tsx similarity index 63% rename from packages/geoview-core/src/api/plugin/map-plugin.ts rename to packages/geoview-core/src/api/plugin/map-plugin.tsx index 16a3197cfed..301210e18ee 100644 --- a/packages/geoview-core/src/api/plugin/map-plugin.ts +++ b/packages/geoview-core/src/api/plugin/map-plugin.tsx @@ -1,5 +1,6 @@ import { createRoot } from 'react-dom/client'; import { AbstractPlugin } from './abstract-plugin'; +import { MapContext } from '@/app'; /** ****************************************************************************************************************************** * Map Plugin abstract class. @@ -24,19 +25,16 @@ export abstract class MapPlugin extends AbstractPlugin { * Called when a map plugin is being added */ onAdd(): void { - // If some layers set - if ((this.configObj?.layers as string[]).length > 0) { - // create the swiper container and insert it after top link - const el = document.createElement('div'); - el.setAttribute('id', `${this.pluginProps.mapId}-${this.pluginId}`); - const mapElement = document.getElementById(`mapbox-${this.pluginProps.mapId}`); - mapElement?.insertBefore(el, mapElement.firstChild); + // create the swiper container and insert it after top link + const el = document.createElement('div'); + el.setAttribute('id', `${this.pluginProps.mapId}-${this.pluginId}`); + const mapElement = document.getElementById(`mapbox-${this.pluginProps.mapId}`); + mapElement?.prepend(el); - // create the swiper component and render - const node = this.onCreateContent(); - const root = createRoot(document.getElementById(`${this.pluginProps.mapId}-${this.pluginId}`)!); - root.render(node); - } + // create the swiper component and render + const node = this.onCreateContent(); + const root = createRoot(el); + root.render({node}); } /** diff --git a/packages/geoview-core/src/core/app-start.tsx b/packages/geoview-core/src/core/app-start.tsx index c9d2c7ae6ef..d1b8550f428 100644 --- a/packages/geoview-core/src/core/app-start.tsx +++ b/packages/geoview-core/src/core/app-start.tsx @@ -50,7 +50,7 @@ function AppStart(props: AppStartProps): JSX.Element { // Log logger.logTraceUseMemo('APP-START - mapContextValue', mapId); - return { mapId: mapId as string }; + return { mapId }; }, [mapId]); //! get store values by id because context is not set.... it is the only 2 atomic selector by id diff --git a/packages/geoview-core/src/core/components/guide/guide-panel.tsx b/packages/geoview-core/src/core/components/guide/guide-panel.tsx index 1e9b323a2a4..ef1ec8372b7 100644 --- a/packages/geoview-core/src/core/components/guide/guide-panel.tsx +++ b/packages/geoview-core/src/core/components/guide/guide-panel.tsx @@ -61,7 +61,7 @@ export function GuidePanel({ fullWidth }: GuidePanelType): JSX.Element { const allTabs: TypeValidFooterBarTabsCoreProps | undefined = footerBarConfig?.tabs.core; // fetch the content of general guide items with custom hook - let mdFilePath = '/geoview/locales/markdown/general-content.md'; + let mdFilePath = '/geoview/public/locales/markdown/general-content.md'; if (process.env.NODE_ENV === 'development') mdFilePath = '/locales/markdown/general-content.md'; useFetchAndParseMarkdown(mapId, mdFilePath, t('guide.errorMessage'), setLeftPanelHelpItems); diff --git a/packages/geoview-core/src/core/stores/geoview-store.ts b/packages/geoview-core/src/core/stores/geoview-store.ts index 08741fa7aa4..6cf759c5039 100644 --- a/packages/geoview-core/src/core/stores/geoview-store.ts +++ b/packages/geoview-core/src/core/stores/geoview-store.ts @@ -11,6 +11,7 @@ import { IMapState, initializeMapState } from './store-interface-and-intial-valu import { IMapDataTableState, initialDataTableState } from './store-interface-and-intial-values/data-table-state'; import { ITimeSliderState, initializeTimeSliderState } from './store-interface-and-intial-values/time-slider-state'; import { IGeochartState, initializeGeochartState } from './store-interface-and-intial-values/geochart-state'; +import { ISwiperState, initializeSwiperState } from './store-interface-and-intial-values/swiper-state'; import { IUIState, initializeUIState } from './store-interface-and-intial-values/ui-state'; import { TypeMapFeaturesConfig } from '@/core/types/global-types'; @@ -38,6 +39,7 @@ export interface IGeoviewState { // packages state interface geochartState: IGeochartState; timeSliderState: ITimeSliderState; + swiperState: ISwiperState; } export const geoviewStoreDefinition = (set: TypeSetStore, get: TypeGetStore) => { @@ -61,6 +63,7 @@ export const geoviewStoreDefinition = (set: TypeSetStore, get: TypeGetStore) => // TODO: Change this check for something more generic that checks in appBar too if (config.footerBar?.tabs.core.includes('time-slider')) set({ timeSliderState: initializeTimeSliderState(set, get) }); if (config.footerBar?.tabs.core.includes('geochart')) set({ geochartState: initializeGeochartState(set, get) }); + if (config.corePackages?.includes('swiper')) set({ swiperState: initializeSwiperState(set, get) }); }, // core states @@ -75,6 +78,7 @@ export const geoviewStoreDefinition = (set: TypeSetStore, get: TypeGetStore) => export const geoviewStoreDefinitionWithSubscribeSelector = subscribeWithSelector(geoviewStoreDefinition); +// TODO: Refactor - Indicate why we need to use a fake store? const fakeStore = create()(geoviewStoreDefinitionWithSubscribeSelector); export type GeoviewStoreType = typeof fakeStore; diff --git a/packages/geoview-core/src/core/stores/store-interface-and-intial-values/swiper-state.ts b/packages/geoview-core/src/core/stores/store-interface-and-intial-values/swiper-state.ts new file mode 100644 index 00000000000..6b6b27f04ab --- /dev/null +++ b/packages/geoview-core/src/core/stores/store-interface-and-intial-values/swiper-state.ts @@ -0,0 +1,52 @@ +import { useStore } from 'zustand'; + +import { useGeoViewStore } from '../stores-managers'; +import { TypeGetStore, TypeSetStore } from '../geoview-store'; + +// #region INTERFACES + +export interface ISwiperState { + layerPaths: string[]; + + actions: { + setLayerPaths: (layerPaths: string[]) => void; + }; +} + +// #endregion INTERFACES + +/** + * Initializes a Swiper state object. + * @param {TypeSetStore} set The store set callback function + * @param {TypeSetStore} get The store get callback function + * @returns {ISwiperState} The Swiper state object + */ +export function initializeSwiperState(set: TypeSetStore, get: TypeGetStore): ISwiperState { + const init = { + layerPaths: [], + + // #region ACTIONS + + actions: { + setLayerPaths(layerPaths: string[]) { + set({ + swiperState: { + ...get().swiperState, + layerPaths, + }, + }); + }, + }, + + // #endregion ACTIONS + } as ISwiperState; + + return init; +} + +// ********************************************************** +// Swiper state selectors +// ********************************************************** +export const useSwiperLayerPaths = () => useStore(useGeoViewStore(), (state) => state.swiperState.layerPaths); + +export const useSwiperStoreActions = () => useStore(useGeoViewStore(), (state) => state.swiperState.actions); diff --git a/packages/geoview-core/src/core/utils/config/config.ts b/packages/geoview-core/src/core/utils/config/config.ts index 749289da116..8def967ebd0 100644 --- a/packages/geoview-core/src/core/utils/config/config.ts +++ b/packages/geoview-core/src/core/utils/config/config.ts @@ -1,12 +1,13 @@ /* eslint-disable no-console */ import { TypeDisplayLanguage, TypeListOfLayerEntryConfig, layerEntryIsGroupLayer } from '@/geo/map/map-schema-types'; import { CONST_LAYER_ENTRY_TYPE, TypeGeoviewLayerType } from '@/geo/layer/geoview-layers/abstract-geoview-layers'; +import { logger } from '@/core/utils/logger'; + import { TypeMapFeaturesConfig } from '../../types/global-types'; import { ConfigValidation } from './config-validation'; import { InlineDivConfigReader } from './reader/div-config-reader'; import { JsonConfigReader } from './reader/json-config-reader'; import { URLmapConfigReader } from './reader/url-config-reader'; -import { logger } from '@/core/utils/logger'; // ****************************************************************************************************************************** // ****************************************************************************************************************************** diff --git a/packages/geoview-core/src/geo/layer/layer.ts b/packages/geoview-core/src/geo/layer/layer.ts index cd2ed72ab78..dd36d89e105 100644 --- a/packages/geoview-core/src/geo/layer/layer.ts +++ b/packages/geoview-core/src/geo/layer/layer.ts @@ -1,5 +1,5 @@ -/* eslint-disable no-param-reassign */ - +import BaseLayer from 'ol/layer/Base'; +import LayerGroup from 'ol/layer/Group'; import { GeoCore, layerConfigIsGeoCore } from '@/geo/layer/other/geocore'; import { Geometry } from '@/geo/layer/geometry/geometry'; import { FeatureHighlight } from '@/geo/utils/feature-highlight'; @@ -301,8 +301,8 @@ export class Layer { ); showError(this.mapId, message); - // eslint-disable-next-line no-console - console.log(`Duplicate use of geoview layer identifier ${geoviewLayerConfig.geoviewLayerId} on map ${this.mapId}`); + // Log + logger.logError(`Duplicate use of geoview layer identifier ${geoviewLayerConfig.geoviewLayerId} on map ${this.mapId}`); } /** @@ -313,7 +313,16 @@ export class Layer { * @returns {AbstractGeoViewLayer} Returns the geoview instance associated to the layer path. */ geoviewLayer(layerPath: string): AbstractGeoViewLayer { + // TODO: Refactor - Move this method next to the getGeoviewLayerByLayerPath equivalent. And then rename it? + // The first element of the layerPath is the geoviewLayerId const geoviewLayerInstance = this.geoviewLayers[layerPath.split('/')[0]]; + + // TODO: Check #1857 - Why set the `layerPathAssociatedToTheGeoviewLayer` property on the fly like that? Should likely set this somewhere else than in this function that looks more like a getter. + // TO.DOCONT: It seems `layerPathAssociatedToTheGeoviewLayer` is indeed used many places, notably in applyFilters logic. + // TO.DOCONT: If all those places rely on the `layerPathAssociatedToTheGeoviewLayer` to be set, that logic using layerPathAssociatedToTheGeoviewLayer should be moved over there. + // TO.DOCONT: If there's more other places relying on the `layerPathAssociatedToTheGeoviewLayer`, then it's not ideal, + // TO.DOCONT: because it's assuming/relying on the fact that all those other places use this specific geoviewLayer() prior to do their work. + // TO.DOCONT: There's likely some separation of logic to apply here. Make this function more evident that it 'sets' something, not just 'gets' a GeoViewLayer. geoviewLayerInstance.layerPathAssociatedToTheGeoviewLayer = layerPath; return geoviewLayerInstance; } @@ -394,6 +403,9 @@ export class Layer { (geoviewLayerConfig) => geoviewLayerConfig.geoviewLayerId !== partialLayerPath ); } + + // Log + logger.logInfo(`Layer removed for ${partialLayerPath}`); }; /** @@ -471,7 +483,7 @@ export class Layer { * Asynchronously gets a layer using its id and return the layer data. * If the layer we're searching for has to be processed, set mustBeProcessed to true when awaiting on this method. * This function waits the timeout period before abandonning (or uses the default timeout when not provided). - * Note this function uses the 'Async' suffix only to differentiate it from 'getGeoviewLayerById'. + * Note this function uses the 'Async' suffix to differentiate it from 'getGeoviewLayerById'. * * @param {string} layerID the layer id to look for * @param {string} mustBeProcessed indicate if the layer we're searching for must be found only once processed @@ -508,6 +520,40 @@ export class Layer { throw new Error(`Layer ${geoviewLayerId} not found.`); }; + /** + * Returns the OpenLayer layer associated to a specific layer path. + * @param {string} layerPath The layer path to the layer's configuration. + * + * @returns {BaseLayer | LayerGroup} Returns the OpenLayer layer associated to the layer path. + */ + getLayerByLayerPath = (layerPath: string): BaseLayer | LayerGroup => { + // Return the olLayer object from the registered layers + const olLayer = api.maps[this.mapId].layer.registeredLayers[layerPath]?.olLayer; + if (olLayer) return olLayer; + throw new Error(`Layer at path ${layerPath} not found.`); + }; + + /** + * Asynchronously returns the OpenLayer layer associated to a specific layer path. + * This function waits the timeout period before abandonning (or uses the default timeout when not provided). + * Note this function uses the 'Async' suffix to differentiate it from 'getLayerByLayerPath'. + * @param {string} layerPath The layer path to the layer's configuration. + * + * @returns {BaseLayer | LayerGroup} Returns the OpenLayer layer associated to the layer path. + */ + getLayerByLayerPathAsync = async (layerPath: string, timeout?: number, checkFrequency?: number): Promise => { + // Make sure the open layer has been created, sometimes it can still be in the process of being created + const promisedLayer = await whenThisThen( + () => { + return api.maps[this.mapId].layer.registeredLayers[layerPath]?.olLayer; + }, + timeout, + checkFrequency + ); + // Here, the layer resolved + return promisedLayer!; + }; + /** * Returns a Promise that will be resolved once the given layer is in a processed phase. * This function waits the timeout period before abandonning (or uses the default timeout when not provided). diff --git a/packages/geoview-core/src/geo/map/map-viewer.ts b/packages/geoview-core/src/geo/map/map-viewer.ts index 8d6ccd8b7f8..0086dc3881b 100644 --- a/packages/geoview-core/src/geo/map/map-viewer.ts +++ b/packages/geoview-core/src/geo/map/map-viewer.ts @@ -56,6 +56,7 @@ import { MapEventProcessor } from '@/api/event-processors/event-processor-childr import { AppEventProcessor } from '@/api/event-processors/event-processor-children/app-event-processor'; import { logger } from '@/core/utils/logger'; +// TODO: Refactor - Typo TypeDcoument - actually, remove the type altogether, doesn't seem useful interface TypeDcoument extends Document { webkitExitFullscreen: () => void; msExitFullscreen: () => void; @@ -115,8 +116,15 @@ export class MapViewer { private i18nInstance!: i18n; /** - * Add the map instance to the maps array in the api - * + * Constructor for a MapViewer, setting: + * - the mapId + * - the mapFeaturesConfig + * - i18n + * - appbar, navbar, footerbar + * - modalApi + * - geoviewRenderer + * - basemap + * - layers * @param {TypeMapFeaturesConfig} mapFeaturesConfig map properties * @param {i18n} i18instance language instance */ @@ -142,6 +150,26 @@ export class MapViewer { this.setLayerAddedListener4ThisListOfLayer(listOfGeoviewLayerConfig); } + /** + * Initializes map, layer class and geometries + * + * @param {OLMap} cgpMap The OpenLayers map object + */ + initMap(cgpMap: OLMap): void { + // Set the map + this.map = cgpMap; + + // TODO: Refactor - Is it necessary to set the mapId again? It was set in constructor. Preferably set it at one or the other. + this.mapId = cgpMap.get('mapId'); + + // initialize layers and load the layers passed in from map config if any + this.layer = new Layer(this.mapId); + this.layer.loadListOfGeoviewLayer(this.mapFeaturesConfig.map.listOfGeoviewLayerConfig); + + // check if geometries are provided from url + this.loadGeometries(); + } + /** * Set the layer added event listener and timeout function for the list of geoview layer configurations. * @@ -159,6 +187,10 @@ export class MapViewer { if (payloadIsGeoViewLayerAdded(payload)) { const { geoviewLayer } = payload; + + // Log + logger.logInfo(`GeoView Layer added on map ${geoviewLayer.geoviewLayerId}`, geoviewLayer); + MapEventProcessor.setLayerZIndices(this.mapId); // If metadata are processed if (geoviewLayer.allLayerStatusAreIn(['processed', 'loading', 'loaded', 'error'])) { @@ -174,10 +206,10 @@ export class MapViewer { } /** - * Method used to test all geoview layers status flags to determine if all the metadata of the map are loaded. + * Tests all geoview layers status flags to determine if all the metadata of the map are loaded. * This doesn't mean that all the layers are loaded on the map, Only the metadata are read and processed. * - * @returns true if all geoview layers on the map are loaded or detected as a load error. + * @returns {boolean} true if all geoview layers on the map are loaded or detected as a load error. */ mapIsReady(): boolean { if (this.layer === undefined) return false; @@ -219,23 +251,6 @@ export class MapViewer { }, 250); } - /** - * Initialize layers, basemap and projection - * - * @param cgpMap - */ - initMap(cgpMap: OLMap): void { - this.mapId = cgpMap.get('mapId'); - this.map = cgpMap; - - // initialize layers and load the layers passed in from map config if any - this.layer = new Layer(this.mapId); - this.layer.loadListOfGeoviewLayer(this.mapFeaturesConfig.map.listOfGeoviewLayerConfig); - - // check if geometries are provided from url - this.loadGeometries(); - } - /** * Add a new custom component to the map * @@ -243,6 +258,7 @@ export class MapViewer { * @param {JSX.Element} component the component to add */ addComponent(mapComponentId: string, component: JSX.Element): void { + // TODO: Refactor - Doesn't seem to be used anymore, keep? If keep it, refactor to call a function instead of event.emit. if (mapComponentId && component) { // emit an event to add the component api.event.emit(mapComponentPayload(EVENT_NAMES.MAP.EVENT_MAP_ADD_COMPONENT, this.mapId, mapComponentId, component)); @@ -255,6 +271,7 @@ export class MapViewer { * @param imapComponentIdd the id of the component to remove */ removeComponent(mapComponentId: string): void { + // TODO: Refactor - Doesn't seem to be used anymore, keep? If keep it, refactor to call a function instead of event.emit. if (mapComponentId) { // emit an event to add the component api.event.emit(mapComponentPayload(EVENT_NAMES.MAP.EVENT_MAP_REMOVE_COMPONENT, this.mapId, mapComponentId)); @@ -318,6 +335,8 @@ export class MapViewer { * @param {HTMLElement} element the element to toggle fullscreen on */ setFullscreen(status: boolean, element: TypeHTMLElement): void { + // TODO: Refactor - For reusability, this function should be static and moved to a browser-utilities class + // TO.DOCONT: If we want to keep a function here, in MapViewer, it should just be a redirect to the browser-utilities' // enter fullscreen if (status) { if (element.requestFullscreen) { diff --git a/packages/geoview-geochart/src/index.tsx b/packages/geoview-geochart/src/index.tsx index e78415b9004..cfb9c32f7ff 100644 --- a/packages/geoview-geochart/src/index.tsx +++ b/packages/geoview-geochart/src/index.tsx @@ -56,6 +56,9 @@ class GeoChartFooterPlugin extends FooterPlugin { }, }); + /** + * Overrides the addition of the GeoChart Footer Plugin to make sure to set the chart configs into the store. + */ onAdd(): void { // Initialize the store with geochart provided configuration GeochartEventProcessor.setGeochartCharts(this.pluginProps.mapId, this.configObj.charts); @@ -64,6 +67,10 @@ class GeoChartFooterPlugin extends FooterPlugin { super.onAdd(); } + /** + * Overrides the creation of the content properties of this GeoChart Footer Plugin. + * @returns {TypeTabs} The TypeTabs for the GeoChart Footer Plugin + */ onCreateContentProps(): TypeTabs { // Create element const content = ; @@ -77,6 +84,10 @@ class GeoChartFooterPlugin extends FooterPlugin { }; } + /** + * Overrides when the plugin is selected in the Footer Bar. + * @returns {TypeTabs} The TypeTabs for the GeoChart Footer Plugin + */ onSelected(): void { // Call parent super.onSelected(); diff --git a/packages/geoview-layers-panel/src/layers-list.tsx b/packages/geoview-layers-panel/src/layers-list.tsx index b37eb11304e..8b20706d781 100644 --- a/packages/geoview-layers-panel/src/layers-list.tsx +++ b/packages/geoview-layers-panel/src/layers-list.tsx @@ -12,6 +12,7 @@ import { } from 'geoview-core'; import { sxClasses } from './layers-list.style'; + /** * interface for the layers list properties in layers panel */ diff --git a/packages/geoview-swiper/src/index.tsx b/packages/geoview-swiper/src/index.tsx index bf7acee911d..65909f11223 100644 --- a/packages/geoview-swiper/src/index.tsx +++ b/packages/geoview-swiper/src/index.tsx @@ -1,5 +1,7 @@ import { Cast, toJsonObject, TypeJsonObject, AnySchemaObject } from 'geoview-core'; import { MapPlugin } from 'geoview-core/src/api/plugin/map-plugin'; +import { SwiperEventProcessor } from 'geoview-core/src/api/event-processors/event-processor-children/swiper-event-processor'; +import { logger } from 'geoview-core/src/core/utils/logger'; import schema from '../schema.json'; import defaultConfig from '../default-config-swiper.json'; @@ -10,21 +12,21 @@ import { Swiper } from './swiper'; */ class SwiperPlugin extends MapPlugin { /** - * Return the package schema + * Returns the package schema * * @returns {AnySchemaObject} the package schema */ schema = (): AnySchemaObject => schema; /** - * Return the default config for this package + * Returns the default config for this package * * @returns {TypeJsonObject} the default config */ defaultConfig = (): TypeJsonObject => toJsonObject(defaultConfig); /** - * translations object to inject to the viewer translations + * Translations object to inject to the viewer translations */ translations = toJsonObject({ en: { @@ -41,9 +43,57 @@ class SwiperPlugin extends MapPlugin { }, }); + /** + * Overrides the addition of the Swiper Map Plugin to make sure to set the layer paths from the config into the store. + */ + onAdd(): void { + // Initialize the store with swiper provided configuration + SwiperEventProcessor.setLayerPaths(this.pluginProps.mapId, this.configObj.layers); + + // Call parent + super.onAdd(); + } + + /** + * Overrides the creation of the content of this Swiper Map Plugin. + * @returns {JSX.Element} The JSX.Element representing the Swiper Plugin + */ onCreateContent(): JSX.Element { return ; } + + /** + * Activates the swiper for the layer indicated by the given layer path. + * @param {string} layerPath The layer path to activate swiper functionality + */ + activateForLayer = (layerPath: string) => { + // Check if the layer exists on the map + const layer = this.map().layer.getLayerByLayerPath(layerPath); + if (layer) { + // Add the layer path + SwiperEventProcessor.addLayerPath(this.pluginProps.mapId, layerPath); + } else { + // Log + logger.logError(`Couldn't find the layer with layer path ${layerPath}`); + } + }; + + /** + * Deactivates the swiper for the layer indicated by the given layer path. + * @param {string} layerPath The layer path to deactivate swiper functionality + */ + deActivateForLayer = (layerPath: string) => { + // Remove the layer + SwiperEventProcessor.removeLayerPath(this.pluginProps.mapId, layerPath); + }; + + /** + * Deactivates the swiper for the layer indicated by the given layer path. + */ + deActivateAll = () => { + // Remove all layers + SwiperEventProcessor.removeAll(this.pluginProps.mapId); + }; } export default SwiperPlugin; diff --git a/packages/geoview-swiper/src/swiper-style.ts b/packages/geoview-swiper/src/swiper-style.ts new file mode 100644 index 00000000000..4fd811a538b --- /dev/null +++ b/packages/geoview-swiper/src/swiper-style.ts @@ -0,0 +1,70 @@ +export const sxClasses = { + layerSwipe: { + position: 'absolute', + width: '100%', + height: '100%', + }, + + handle: { + backgroundColor: 'rgba(50,50,50,0.75)', + color: '#fff', + width: '24px', + height: '24px', + }, + + bar: { + position: 'absolute', + backgroundColor: 'rgba(50,50,50,0.75)', + zIndex: 151, + boxSizing: 'content-box', + margin: 0, + padding: '0!important', + }, + + vertical: { + width: '8px', + height: '100%', + cursor: 'col-resize', + top: '0px!important', + + '& .handleContainer': { + position: 'relative', + width: '58px', + height: '24px', + zIndex: 1, + top: '50%', + left: '-25px', + + '& .handleR': { + transform: 'rotate(90deg)', + float: 'right', + }, + + '& .handleL': { + transform: 'rotate(90deg)', + float: 'left', + }, + }, + }, + + horizontal: { + width: '100%', + height: '8px', + cursor: 'col-resize', + left: '0px!important', + + '& .handleContainer': { + position: 'relative', + height: '58px', + width: '24px', + zIndex: 1, + left: '50%', + top: '-24px', + + '& .handleL': { + verticalAlign: 'top', + marginBottom: '8px', + }, + }, + }, +}; diff --git a/packages/geoview-swiper/src/swiper.tsx b/packages/geoview-swiper/src/swiper.tsx index c908bd32c85..029e33910ea 100644 --- a/packages/geoview-swiper/src/swiper.tsx +++ b/packages/geoview-swiper/src/swiper.tsx @@ -1,93 +1,20 @@ import Draggable from 'react-draggable'; -import { RefObject, useAppDisplayLanguageById } from 'geoview-core'; - +import { RefObject } from 'geoview-core'; +import { MapViewer } from 'geoview-core/src/geo/map/map-viewer'; +import { useSwiperLayerPaths } from 'geoview-core/src/core/stores/store-interface-and-intial-values/swiper-state'; import { getLocalizedMessage } from 'geoview-core/src/core/utils/utilities'; -import { EVENT_NAMES } from 'geoview-core/src/api/events/event-types'; -import { PayloadBaseClass, TypeResultsSet, payloadIsLayerSetUpdated } from 'geoview-core/src/api/events/payloads'; import { logger } from 'geoview-core/src/core/utils/logger'; import { getRenderPixel } from 'ol/render'; -import Map from 'ol/Map'; import RenderEvent from 'ol/render/Event'; import BaseLayer from 'ol/layer/Base'; -import { VectorImage } from 'ol/layer'; -import VectorSource from 'ol/source/Vector'; import { EventTypes } from 'ol/Observable'; import BaseEvent from 'ol/events/Event'; import debounce from 'lodash/debounce'; -const sxClasses = { - layerSwipe: { - position: 'absolute', - width: '100%', - height: '100%', - }, - - handle: { - backgroundColor: 'rgba(50,50,50,0.75)', - color: '#fff', - width: '24px', - height: '24px', - }, - - bar: { - position: 'absolute', - backgroundColor: 'rgba(50,50,50,0.75)', - zIndex: 151, - boxSizing: 'content-box', - margin: 0, - padding: '0!important', - }, - - vertical: { - width: '8px', - height: '100%', - cursor: 'col-resize', - top: '0px!important', - - '& .handleContainer': { - position: 'relative', - width: '58px', - height: '24px', - zIndex: 1, - top: '50%', - left: '-25px', - - '& .handleR': { - transform: 'rotate(90deg)', - float: 'right', - }, - - '& .handleL': { - transform: 'rotate(90deg)', - float: 'left', - }, - }, - }, - - horizontal: { - width: '100%', - height: '8px', - cursor: 'col-resize', - left: '0px!important', - - '& .handleContainer': { - position: 'relative', - height: '58px', - width: '24px', - zIndex: 1, - left: '50%', - top: '-24px', - - '& .handleL': { - verticalAlign: 'top', - marginBottom: '8px', - }, - }, - }, -}; +import { sxClasses } from './swiper-style'; type SwiperProps = { mapId: string; @@ -104,65 +31,53 @@ export function Swiper(props: SwiperProps): JSX.Element { const { cgpv } = window; const { api, ui, react } = cgpv; - const { useEffect, useState, useRef } = react; - + const { useEffect, useState, useRef, useCallback } = react; const { Box, Tooltip, HandleIcon } = ui.elements; + const { orientation } = config; + const mapViewer = api.maps[mapId] as MapViewer; - const [map] = useState(api.maps[mapId].map); - const mapSize = useRef(map?.getSize() || [0, 0]); - const defaultX = mapSize.current[0] / 2; - const defaultY = mapSize.current[1] / 2; - const [olLayers, setOlLayers] = useState([]); - - const [orientation] = useState(config.orientation); - + const mapSize = useRef(mapViewer.map?.getSize() || [0, 0]); const swiperValue = useRef(50); const swiperRef = useRef(); - // Get store value - const displayLanguage = useAppDisplayLanguageById(mapId); - - /** - * Sort layers to only include those that are loaded - * @param {TypeResultsSet} resultsSets The resulstSet from the layer set - * - * @returns {string[]} array of IDs for layers that are loaded on the map - */ - function sortLayerIds(resultsSets: TypeResultsSet) { - const layerIds: string[] = []; - Object.keys(resultsSets).forEach((result) => { - if (resultsSets[result].layerStatus === 'loaded') layerIds.push(result.split('/')[0]); - }); - return layerIds; - } + const [olLayers, setOlLayers] = useState([]); + const [xPosition, setXPosition] = useState(mapSize.current[0] / 2); + const [yPosition, setYPosition] = useState(mapSize.current[1] / 2); - const [layersIds, setLayersIds] = useState(sortLayerIds(api.getLegendsLayerSet(mapId).resultsSet)); + // Get store values + const layerPaths = useSwiperLayerPaths(); /** * Pre compose, Pre render event callback * @param {Event | BaseEvent} event The pre compose, pre render event */ - function prerender(event: Event | BaseEvent) { - const evt = event as RenderEvent; - const ctx: CanvasRenderingContext2D = evt.context! as CanvasRenderingContext2D; - const width = ((mapSize.current[0] + 6) * swiperValue.current) / 100; - const height = ((mapSize.current[1] + 6) * swiperValue.current) / 100; - - const tl = getRenderPixel(evt, [0, 0]); - const tr = orientation === 'vertical' ? getRenderPixel(evt, [width, 0]) : getRenderPixel(evt, [mapSize.current[0], 0]); - const bl = orientation === 'vertical' ? getRenderPixel(evt, [0, mapSize.current[1]]) : getRenderPixel(evt, [0, height]); - const br = - orientation === 'vertical' ? getRenderPixel(evt, [width, mapSize.current[1]]) : getRenderPixel(evt, [mapSize.current[0], height]); - - ctx.save(); - ctx.beginPath(); - ctx.moveTo(tl[0], tl[1]); - ctx.lineTo(bl[0], bl[1]); - ctx.lineTo(br[0], br[1]); - ctx.lineTo(tr[0], tr[1]); - ctx.closePath(); - ctx.clip(); - } + const prerender = useCallback( + (event: Event | BaseEvent) => { + // Log + logger.logTraceUseCallback('GEOVIEW-SWIPER - prerender', event); + + const evt = event as RenderEvent; + const ctx: CanvasRenderingContext2D = evt.context! as CanvasRenderingContext2D; + const width = ((mapSize.current[0] + 6) * swiperValue.current) / 100; + const height = ((mapSize.current[1] + 6) * swiperValue.current) / 100; + + const tl = getRenderPixel(evt, [0, 0]); + const tr = orientation === 'vertical' ? getRenderPixel(evt, [width, 0]) : getRenderPixel(evt, [mapSize.current[0], 0]); + const bl = orientation === 'vertical' ? getRenderPixel(evt, [0, mapSize.current[1]]) : getRenderPixel(evt, [0, height]); + const br = + orientation === 'vertical' ? getRenderPixel(evt, [width, mapSize.current[1]]) : getRenderPixel(evt, [mapSize.current[0], height]); + + ctx.save(); + ctx.beginPath(); + ctx.moveTo(tl[0], tl[1]); + ctx.lineTo(bl[0], bl[1]); + ctx.lineTo(br[0], br[1]); + ctx.lineTo(tr[0], tr[1]); + ctx.closePath(); + ctx.clip(); + }, + [orientation] + ); /** * Post compose, Post render event callback @@ -203,85 +118,21 @@ export function Swiper(props: SwiperProps): JSX.Element { */ const onStop = debounce(() => { // get map size - mapSize.current = map.getSize() || [0, 0]; + mapSize.current = mapViewer.map.getSize() || [0, 0]; const size = orientation === 'vertical' ? mapSize.current[0] : mapSize.current[1]; const position = orientation === 'vertical' ? getSwiperStyle()[0] : getSwiperStyle()[1]; swiperValue.current = (position / size) * 100; - // force VectorImage to refresh + // Update the position + if (orientation === 'vertical') setXPosition(position); + if (orientation === 'vertical') setYPosition(position); + + // Force refresh olLayers.forEach((layer: BaseLayer) => { - if (layer !== null && typeof (layer as VectorImage).getImageRatio === 'function') layer.changed(); + layer.changed(); }); - - map.render(); }, 100); - /** - * Set the prerender and postrender events - * - * @param {string} layer the layer name - */ - const setRenderEvents = (layer: string) => { - const { geoviewLayers } = api.maps[mapId].layer; - const olLayer = geoviewLayers[layer].olLayers; - setOlLayers((prevArray: BaseLayer[]) => [...prevArray, olLayer!]); - olLayer?.on(['precompose' as EventTypes, 'prerender' as EventTypes], prerender); - olLayer?.on(['postcompose' as EventTypes, 'postrender' as EventTypes], postcompose); - // force VectorImage to refresh - if (olLayer !== null && typeof (olLayer as VectorImage).getImageRatio === 'function') olLayer?.changed(); - }; - - useEffect(() => { - // Log - logger.logTraceUseEffect('GEOVIEW-SWIPER - layersIds.displayLanguage', layersIds, displayLanguage); - - // set listener for layers in config array - const { geoviewLayers } = api.maps[mapId].layer; - layersIds.forEach((layer: string) => { - setRenderEvents(layer); - }); - - return () => { - layersIds.forEach((layer: string) => { - if (geoviewLayers[layer] !== undefined) { - const olLayer = geoviewLayers[layer].olLayers; - olLayer?.un(['precompose' as EventTypes, 'prerender' as EventTypes], prerender); - olLayer?.un(['postcompose' as EventTypes, 'postrender' as EventTypes], postcompose); - - // empty layers array - setOlLayers([]); - } - }); - }; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [layersIds, displayLanguage]); - - // Update layer list if a layer loads late - useEffect(() => { - // Log - logger.logTraceUseEffect('GEOVIEW-SWIPER - layersIds', layersIds); - - const layerSetUpdatedHandler = (payload: PayloadBaseClass) => { - // Log - logger.logTraceCoreAPIEvent('GEOVIEW-SWIPER - layerSetUpdatedHandler', payload); - - if (payloadIsLayerSetUpdated(payload) && payload.resultsSet[payload.layerPath]?.layerStatus === 'loaded') { - const layerId = payload.layerPath.split('/')[0]; - const ids = [...layersIds]; - if (ids.indexOf(layerId) === -1 && config.layers.includes(layerId)) { - ids.push(layerId); - setLayersIds(ids); - } - } - }; - - api.event.on(EVENT_NAMES.LAYER_SET.UPDATED, layerSetUpdatedHandler, `${mapId}/LegendsLayerSet`); - return () => { - api.event.off(EVENT_NAMES.LAYER_SET.UPDATED, mapId, layerSetUpdatedHandler); - }; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [layersIds]); - /** * Update swiper and layers from keyboard CTRL + Arrow key * @param {KeyboardEvent} evt The keyboard event to calculate the swiper position @@ -309,36 +160,135 @@ export function Swiper(props: SwiperProps): JSX.Element { } }, 100); - // set listener for the focus in on swiper bar when on WCAG mode - // unset listener when focus is out of swiper bar - swiperRef?.current?.addEventListener('focusin', () => { - if (document.getElementById(mapId)!.classList.contains('map-focus-trap')) { - swiperRef?.current?.addEventListener('keydown', updateSwiper); - } - }); - swiperRef?.current?.addEventListener('focusout', () => { - swiperRef?.current?.removeEventListener('keydown', updateSwiper); - }); - - return ( - - onStop()} - onDrag={() => onStop()} - nodeRef={swiperRef as RefObject} - > - - - - - - - - - - + /** + * Attaches necessary swiper events to the given layer path layer + * @param {string} layerPath The layer path of the layer to attach swiping events to + */ + const attachLayerEventsOnPath = useCallback( + async (layerPath: string) => { + try { + // Get the layer at the layer path + const olLayer = await mapViewer.layer.getLayerByLayerPathAsync(layerPath); + if (olLayer) { + // Set the OL layers + setOlLayers((prevArray: BaseLayer[]) => [...prevArray, olLayer]); + + // Wire events on the layer + olLayer.on(['precompose' as EventTypes, 'prerender' as EventTypes], prerender); + olLayer.on(['postcompose' as EventTypes, 'postrender' as EventTypes], postcompose); + + // Force refresh + olLayer.changed(); + } else { + // Log + logger.logError('SWIPER - Failed to find layer to attach layer events', layerPath); + } + } catch (error) { + // Log + logger.logError('SWIPER - Failed to attach layer events', mapViewer.layer?.geoviewLayers, layerPath, error); + } + }, + [mapViewer, prerender] ); + + useEffect(() => { + // Log + logger.logTraceUseEffect('GEOVIEW-SWIPER - layerPaths', layerPaths); + + // For each layer path + layerPaths.forEach((layerPath: string) => { + // Wire events on the layer path + attachLayerEventsOnPath(layerPath); + }); + + return () => { + // Log + logger.logTraceUseEffectUnmount('GEOVIEW-SWIPER - layerPaths', layerPaths); + + // set listener for layers in config array + layerPaths.forEach((layerPath: string) => { + try { + // Get the layer at the layer path + const olLayer = mapViewer.layer.getLayerByLayerPath(layerPath); + if (olLayer) { + // Unwire the events on the layer + olLayer.un(['precompose' as EventTypes, 'prerender' as EventTypes], prerender); + olLayer.un(['postcompose' as EventTypes, 'postrender' as EventTypes], postcompose); + + // Force refresh + olLayer.changed(); + } else { + // Log + logger.logError('SWIPER - Failed to find layer to un-attach layer events', layerPath); + } + } catch (error) { + // Log + logger.logError('SWIPER - Failed to un-attach layer events', layerPath, error); + } + }); + + // Empty layers array + setOlLayers([]); + }; + }, [mapViewer, layerPaths, attachLayerEventsOnPath, prerender]); + + useEffect(() => { + // Log + logger.logTraceUseEffect('GEOVIEW-SWIPER - mount', mapId); + + // Grab reference + const theSwiper = swiperRef?.current; + + const handleFocusIn = () => { + // set listener for the focus in on swiper bar when on WCAG mode + if (document.getElementById(mapId)!.classList.contains('map-focus-trap')) { + theSwiper?.addEventListener('keydown', updateSwiper); + } + }; + + const handleFocusOut = () => { + // unset listener when focus is out of swiper bar + theSwiper?.removeEventListener('keydown', updateSwiper); + }; + + // Wire events + theSwiper?.addEventListener('focusin', handleFocusIn); + theSwiper?.addEventListener('focusout', handleFocusOut); + + return () => { + // Log + logger.logTraceUseEffectUnmount('GEOVIEW-SWIPER - unmount', mapId); + + // Unwire events + theSwiper?.removeEventListener('focusout', handleFocusOut); + theSwiper?.removeEventListener('focusin', handleFocusIn); + }; + }, [mapId, updateSwiper]); + + // If any layer paths + if (layerPaths.length > 0) { + // Use a swiper + return ( + + onStop()} + onDrag={() => onStop()} + nodeRef={swiperRef as RefObject} + > + + + + + + + + + + + ); + } + return ; }