Skip to content

Commit

Permalink
Projections tests (#1863)
Browse files Browse the repository at this point in the history
Projections working
Some comments
Removed an old eslint
Rush update --removed actually
Getting rid of 2 eslint warnings
Removed dead code
Removed comment about renaming the config types/classes
  • Loading branch information
Alex-NRCan authored Feb 29, 2024
1 parent c818df0 commit 3c155ba
Show file tree
Hide file tree
Showing 10 changed files with 156 additions and 37 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import {
} from '@/geo';
import { TypeLegendLayer, TypeLegendLayerIcons, TypeLegendLayerItem, TypeLegendItem } from '@/core/components/layers/types';
import { api, getLocalizedValue, ILayerState } from '@/app';
import { logger } from '@/core/utils/logger';

import { AbstractEventProcessor } from '../abstract-event-processor';

Expand All @@ -27,13 +26,6 @@ export class LegendEventProcessor extends AbstractEventProcessor {
//! ALWAYS use map event processor when an action modify store and IS NOT trap by map state event handler

// #region
// Indicate if the processor has been propagated once yet
private static propagatedOnce = false;

// The time delay before selecting a layer in the store upon first legend propagation.
// The longer the delay, the more chances layers will be loaded state at the time of picking a layer to be selected.
// The longer the delay, the later a layer will be selected in the store upon initial propagation.
private static timeDelayBeforeSelectingLayerInStore = 2000;

/**
* Shortcut to get the Layer state for a given map id
Expand Down Expand Up @@ -217,26 +209,6 @@ export class LegendEventProcessor extends AbstractEventProcessor {

// Update the legend layers with the updated array, triggering the subscribe
this.getLayerState(mapId).actions.setLegendLayers(layers);

// Check if this is an initial load
if (!LegendEventProcessor.propagatedOnce) {
// Flag so this is only executed once after initial load
LegendEventProcessor.propagatedOnce = true;

// TODO: The selected layer issue will be tackle ASAP in a next PR
// Find the layers that are processed
const validFirstLayer = layers.find((layer) => {
return layer.layerStatus === 'processed';
});

// If found a valid first layer to select
if (validFirstLayer) {
// Set the selected layer path in the store
this.getLayerState(mapId).actions.setSelectedLayerPath(validFirstLayer.layerPath);
// Log
logger.logDebug(`Selected layer ${validFirstLayer.layerPath}`);
}
}
}
// #endregion

Expand Down
8 changes: 4 additions & 4 deletions packages/geoview-core/src/api/events/event.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
/* eslint-disable no-underscore-dangle */
import EventEmitter from 'eventemitter3';

import { EventStringId } from './event-types';
Expand All @@ -12,7 +11,7 @@ type TypeEventNode = {
once: boolean;
};

interface TypeEventEmitter extends EventEmitter {
interface IEventEmitter extends EventEmitter {
_events: Record<string, TypeEventNode | TypeEventNode[]>;
_eventsCount: number;
}
Expand All @@ -25,13 +24,13 @@ interface TypeEventEmitter extends EventEmitter {
*/
export class Event {
// event emitter object, used to handle emitting/subscribing to events
eventEmitter: TypeEventEmitter;
eventEmitter: IEventEmitter;

/**
* Initiate the event emitter
*/
constructor() {
this.eventEmitter = new EventEmitter() as TypeEventEmitter;
this.eventEmitter = new EventEmitter() as IEventEmitter;
}

/**
Expand Down Expand Up @@ -86,6 +85,7 @@ export class Event {
* @param {string} eventTypeToKeep the handler name prefix composed of handlerNamePrefix/eventTypeToKeep to keep
*/
offAll = (handlerNamePrefix: string, eventTypeToKeep?: string): void => {
// eslint-disable-next-line no-underscore-dangle
(Object.keys(this.eventEmitter._events) as EventStringId[]).forEach((eventNameId) => {
if (eventNameId.startsWith(handlerNamePrefix)) {
if (eventTypeToKeep) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,5 @@ export default function ExportButton(props: ExportProps): JSX.Element {
*/
ExportButton.defaultProps = {
className: '',
sxDetails: undefined,
};
3 changes: 2 additions & 1 deletion packages/geoview-core/src/core/components/map/map.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,8 @@ export function Map(): JSX.Element {

// Init the map on first render
initMap();
}, [initMap]);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []); // We don't want to add a dependency here, because we only want this execution path to be executed on original mount of the map. Never again afterwards as it causes duplications of Views.

return (
/* eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex */
Expand Down
20 changes: 19 additions & 1 deletion packages/geoview-core/src/core/stores/geoview-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { IUIState, initializeUIState } from './store-interface-and-intial-values

import { TypeMapFeaturesConfig } from '@/core/types/global-types';
import { logger } from '@/core/utils/logger';
import { serializeTypeGeoviewLayerConfig } from '@/geo/map/map-schema-types';

export type TypeSetStore = (
partial: IGeoviewState | Partial<IGeoviewState> | ((state: IGeoviewState) => IGeoviewState | Partial<IGeoviewState>),
Expand Down Expand Up @@ -50,9 +51,26 @@ export const geoviewStoreDefinition = (set: TypeSetStore, get: TypeGetStore) =>
return {
mapConfig: undefined,
setMapConfig: (config: TypeMapFeaturesConfig) => {
// Log (leaving the logDebug for now until more tests are done with the config 2024-02-28)
logger.logDebug('Sending the map config to the store...');

// ! this is a copy of the original map configuration, no modifications is allowed
// ? this configuration is use to reload the map
set({ mapConfig: cloneDeep(config), mapId: config.mapId });
const clonedConfig = cloneDeep(config);

// Serialize the configuration so that it goes in the store without any mutable class instances
// TODO: Refactor - Remove class instances for configuration level objects.
// TO.DOCONT: Indeed, using classes such as `OLLayer` in a low-level configuration class makes the configuration class hard to scale and port.
// TO.DOCONT: Configurations should be as losely coupled as possible.
for (let i = 0; i < (clonedConfig.map?.listOfGeoviewLayerConfig?.length || 0); i++) {
// Serialize the GeoviewLayerConfig
const serialized = serializeTypeGeoviewLayerConfig(clonedConfig.map!.listOfGeoviewLayerConfig![i]);

// Reassign
clonedConfig.map.listOfGeoviewLayerConfig![i] = serialized as never;
}

set({ mapConfig: clonedConfig, mapId: config.mapId });

// initialize default stores section from config information
get().appState.setDefaultConfigValues(config);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -362,7 +362,44 @@ export abstract class AbstractGeoViewLayer {
? api.dateUtilities.getDateFragmentsOrder(mapLayerConfig.serviceDateFormat)
: undefined;
this.externalFragmentsOrder = api.dateUtilities.getDateFragmentsOrder(mapLayerConfig.externalDateFormat);

// TODO: Refactor - This assignation logic in the api...geoviewLayers array should be outside of a constructor logic.
// TO.DOCONT: If this was written to make sure all created geoview layers, anywhere, automatically appear in the api array, then
// TO.DOCONT: I'd suggest having that logic elsewhere and allow the devs/framework to create geoview layers, by code, that do not
// TO.DOCONT: necessarily jump in an api array and possibly affect other code just because an object was instanciated.
api.maps[mapId].layer.geoviewLayers[this.geoviewLayerId] = this;

// TODO: Refactor - This call to `setListOfLayerEntryConfig` does a lot more than a simple 'setter' and should be outside of a constructor logic.
// TO.DOCONT: The function should be renamed and a lot more documentation should be associated with the function to detail what it does.
// TO.DOCONT: Notably important is what it does when there's a least one `mapLayerConfig.listOfLayerEntryConfig`(!). After a quick read, it does the following:
// TO.DOCONT: 1- Sets the `this.listOfLayerEntryConfig`to the provided `mapLayerConfig.listOfLayerEntryConfig` OR
// TO.DOCONT: if there's more than 1 `mapLayerConfig.listOfLayerEntryConfig` it creates a new `TypeLayerGroupEntryConfig` class
// TO.DOCONT: and then it loops on each `mapLayerConfig.listOfLayerEntryConfig` to set the parentLayerConfig.
// TO.DOCONT: 2- Then, it calls `initRegisteredLayers` which loops on the `mapLayerConfig.listOfLayerEntryConfig` to attach the
// TO.DOCONT: layerConfig.geoviewLayerInstance property to a reference of `this` (also coupling it with the `api...layer`)
// TO.DOCONT: 3- Then, it calls `registerLayerConfig` on each `mapLayerConfig.listOfLayerEntryConfig` which attaches the
// TO.DOCONT: (api...layer.registeredLayers[this.layerPath] as ConfigBaseClass) to each `mapLayerConfig.listOfLayerEntryConfig`
// TO.DOCONT: 4- Then, it calls `(this.geoviewLayerInstance as AbstractGeoViewLayer).registerToLayerSets` on `this` (which is
// TO.DOCONT: technically still being constructed at this point) and wires a series of event handlers on the `api.event`
// TO.DOCONT: Here are some notes for discussion and I could be wrong/missunderstanding on some points, but:
// TO.DOCONT: Note 1 - the `registerToLayerSets` is also called via other patterns like via `processListOfLayerEntryConfig` which is
// TO.DOCONT: also a function processing the `listOfLayerEntryConfig`, making it difficult to know where the code must be modified to edit the behavior.
// TO.DOCONT: Indeed, on one hand, some processing on the `listOfLayerEntryConfig` is done as part of the constructor and on the other hand via
// TO.DOCONT: a function such as `createGeoViewLayers`, adding to the confusion.
// TO.DOCONT: Note 2 - `setlistOfLayerEntryConfig` launches a series of api.event which continues executing long after the call
// TO.DOCONT: to `setlistOfLayerEntryConfig` has returned and the propagation to the store happen in parallel with other code being executed inside
// TO.DOCONT: functions such as `createGeoViewLayers`.
// TO.DOCONT: Note 3 - to be confirmed, it's possible the information being propagated to the store during this execution will vary depending on the time
// TO.DOCONT: the propagation happens and the state of the mutating layerConfig object.
// TO.DOCONT: Note 4 - the `setListOfLayerEntryConfig` is also manually called in `add-new-layer` which overrides the listOfLayerEntryConfig set
// TO.DOCONT: in the constructor (maybe that's by-design here, but is confusing, because that's possibly doubling (unless all correctly bypassed?)
// TO.DOCONT: the raising and handling of api.events and also slowing down the code). Furthermore, in another place in in `add-new-layer`
// TO.DOCONT: when going through the steps to add a layer, a new layer instance is created, triggering
// TO.DOCONT: this `setListOfLayerEntryConfig` line below, but because the `mapLayerConfig.listOfLayerEntryConfig` is an empty array it seems to
// TO.DOCONT: save the situation of not hitting the `initRegisteredLayers` line in `setListOfLayerEntryConfig` and attach multiple api.events.
// TO.DOCONT: That's a relief, because the user can move through the steps and create multiple instances of layers to validate them and even
// TO.DOCONT: cancel the addition. If that's by design, it should be clarified. Hopefully nobody in code creates a layer with a
// TO.DOCONT: `mapLayerConfig.listOfLayerEntryConfig` already set and cancels though(!)
this.setListOfLayerEntryConfig(mapLayerConfig, mapLayerConfig.listOfLayerEntryConfig);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,7 @@ export class GeoPackage extends AbstractGeoViewVector {
*
* @returns {Promise<BaseLayer | null>} The promise that the layers were processed.
*/
// TODO: Question - Is this function still used or should it be removed in favor of the mother class implementation?
processListOfLayerEntryConfig(listOfLayerEntryConfig: TypeListOfLayerEntryConfig, layerGroup?: LayerGroup): Promise<BaseLayer | null> {
this.setLayerPhase('processListOfLayerEntryConfig');
const promisedListOfLayerEntryProcessed = new Promise<BaseLayer | null>((resolve) => {
Expand Down
3 changes: 1 addition & 2 deletions packages/geoview-core/src/geo/layer/other/geocore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,8 +125,7 @@ export class GeoCore {
const uuid = layerConfig.layerId;

try {
// Get the GV config from UUID and await even if within loop
// eslint-disable-next-line no-await-in-loop
// Get the GV config from UUID and await
const response = await UUIDmapConfigReader.getGVConfigFromUUIDs(url, lang, [uuid]);

// For each found layer associated with the Geocore UUIDs
Expand Down
91 changes: 90 additions & 1 deletion packages/geoview-core/src/geo/map/map-schema-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { AbstractGeoViewLayer, TypeGeoviewLayerType } from '@/geo/layer/geoview-
import { TypeMapMouseInfo } from '@/api/events/payloads';
import { createLocalizedString } from '@/core/utils/utilities';
import { logger } from '@/core/utils/logger';
import { Cast, LayerSetPayload } from '@/core/types/cgpv-types';
import { Cast, LayerSetPayload, TypeJsonValue } from '@/core/types/cgpv-types';
import { api } from '@/app';

/** ******************************************************************************************************************************
Expand Down Expand Up @@ -662,6 +662,8 @@ export class ConfigBaseClass {
/** Layer entry data type. This element is part of the schema. */
entryType?: TypeLayerEntryType;

// TODO: There shouldn't be a coupling to a `AbstractGeoViewLayer` inside a Configuration class.
// TO.DOCONT: That logic should be elsewhere so that the Configuration class remains portable and immutable.
/** The geoview layer instance that contains this layer configuration. */
geoviewLayerInstance?: AbstractGeoViewLayer;

Expand All @@ -675,12 +677,16 @@ export class ConfigBaseClass {
* metadata. */
isMetadataLayerGroup?: boolean;

// TODO: There shouldn't be a coupling to a `TypeLayerGroupEntryConfig` inside a Configuration class.
// TO.DOCONT: That logic should be elsewhere so that the Configuration class remains portable and immutable.
/** It is used to link the layer entry config to the parent's layer config. */
parentLayerConfig?: TypeGeoviewLayerConfig | TypeLayerGroupEntryConfig;

/** The layer path to this instance. */
protected _layerPath = '';

// TODO: There shouldn't be a coupling to a `BaseLayer` (OpenLayer!) inside a Configuration class.
// TO.DOCONT: That logic should be elsewhere so that the Configuration class remains portable and immutable.
/** This property is used to link the displayed layer to its layer entry config. it is not part of the schema. */
protected _olLayer: BaseLayer | LayerGroup | null = null;

Expand Down Expand Up @@ -821,6 +827,7 @@ export class ConfigBaseClass {
*
* @returns {AbstractGeoViewLayer} Returns the geoview instance associated to the layer path.
*/
// TODO: Check - Is this still used? Remove it and favor the homonymous method in `layer`?
geoviewLayer(layerPath?: string): AbstractGeoViewLayer {
this.geoviewLayerInstance!.layerPathAssociatedToTheGeoviewLayer = layerPath || this.layerPath;
return this.geoviewLayerInstance!;
Expand All @@ -837,6 +844,30 @@ export class ConfigBaseClass {
IsGreaterThanOrEqualTo(layerStatus: TypeLayerStatus): boolean {
return this.layerStatusWeight[this.layerStatus] >= this.layerStatusWeight[layerStatus];
}

/**
* Serializes the ConfigBaseClass class
* @returns {TypeJsonValue} The serialized ConfigBaseClass
*/
serialize(): TypeJsonValue {
// Redirect
return this.onSerialize();
}

/**
* Overridable function to serialize a ConfigBaseClass
* @returns {TypeJsonValue} The serialized ConfigBaseClass
*/
onSerialize(): TypeJsonValue {
return {
layerIdExtension: this.layerIdExtension,
schemaTag: this.schemaTag,
entryType: this.entryType,
layerStatus: this.layerStatus,
layerPhase: this.layerPhase,
isMetadataLayerGroup: this.isMetadataLayerGroup,
} as unknown as TypeJsonValue;
}
}

/** ******************************************************************************************************************************
Expand Down Expand Up @@ -941,6 +972,32 @@ export abstract class TypeBaseLayerEntryConfig extends ConfigBaseClass {
if (this._layerStatus === 'loaded')
api.event.emit(LayerSetPayload.createLayerSetChangeLayerStatusPayload(this.geoviewLayerInstance!.mapId, this.layerPath, 'loaded'));
}

/**
* Serializes the TypeBaseLayerEntryConfig class
* @returns {TypeJsonValue} The serialized TypeBaseLayerEntryConfig
*/
serialize(): TypeJsonValue {
// Redirect
return this.onSerialize();
}

/**
* Overrides the serialization of the mother class
* @returns {TypeJsonValue} The serialized TypeBaseLayerEntryConfig
*/
onSerialize(): TypeJsonValue {
// Call parent
const serialized = super.onSerialize() as unknown as TypeBaseLayerEntryConfig;

// Copy values
serialized.layerIdExtension = this.layerIdExtension;
serialized.layerName = this.layerName;
serialized.initialSettings = this.initialSettings;

// Return it
return serialized as unknown as TypeJsonValue;
}
}

/** ******************************************************************************************************************************
Expand Down Expand Up @@ -1660,6 +1717,38 @@ export type TypeGeoviewLayerConfig = {
listOfLayerEntryConfig: TypeListOfLayerEntryConfig;
};

/**
* Temporary? function to serialize a geoview layer configuration to be able to send it to the store
* @param {TypeGeoviewLayerConfig} geoviewLayerConfig The geoviewlayer config to serialize
* @returns TypeJsonValue The serialized config as pure JSON
*/
export const serializeTypeGeoviewLayerConfig = (geoviewLayerConfig: TypeGeoviewLayerConfig): TypeJsonValue => {
// TODO: Create a 'serialize()' function inside `TypeGeoviewLayerConfig` when/if it's transformed to a class.
// TO.DOCONT: and copy this code in deleting this function here. For now, this explicit workaround function is necessary.
const serializedGeoviewLayerConfig = {
geoviewLayerId: geoviewLayerConfig.geoviewLayerId,
geoviewLayerName: geoviewLayerConfig.geoviewLayerName,
metadataAccessPath: geoviewLayerConfig.metadataAccessPath,
geoviewLayerType: geoviewLayerConfig.geoviewLayerType,
serviceDateFormat: geoviewLayerConfig.serviceDateFormat,
externalDateFormat: geoviewLayerConfig.externalDateFormat,
initialSettingss: geoviewLayerConfig.initialSettings,
listOfLayerEntryConfig: [],
} as TypeGeoviewLayerConfig;

// Loop on the LayerEntryConfig to serialize further
for (let j = 0; j < (geoviewLayerConfig.listOfLayerEntryConfig?.length || 0); j++) {
// Serialize the TypeLayerEntryConfig
const serializedLayerEntryConfig = geoviewLayerConfig.listOfLayerEntryConfig[j].serialize();

// Store
serializedGeoviewLayerConfig.listOfLayerEntryConfig.push(serializedLayerEntryConfig as never);
}

// Return it
return serializedGeoviewLayerConfig as never;
};

/** ******************************************************************************************************************************
* Definition of the view settings.
*/
Expand Down
1 change: 1 addition & 0 deletions packages/geoview-core/src/ui/popper/popper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React, { useEffect, useRef } from 'react';
import { Popper as MaterialPopper, PopperProps } from '@mui/material';

interface EnhancedPopperProps extends PopperProps {
// eslint-disable-next-line react/require-default-props
onClose?: () => void;
}

Expand Down

0 comments on commit 3c155ba

Please sign in to comment.