Skip to content

Commit

Permalink
refactor(store): All state follow setterActions (#2154)
Browse files Browse the repository at this point in the history
* refactor(state): use the setterAction paradigm
Closes #2148

* fix time slider

* fix build

* rebase

---------

Co-authored-by: jolevesq <[email protected]>
  • Loading branch information
jolevesq and jolevesq authored May 21, 2024
1 parent da7d45b commit 75daf6b
Show file tree
Hide file tree
Showing 27 changed files with 288 additions and 152 deletions.
2 changes: 1 addition & 1 deletion packages/geoview-core/public/templates/sandbox.html
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ <h4 id="HLCONF1">Sanbox Map</h4>

// create map
// TODO: the delete has a timeout so we need to wait before trying to recreate the map...
// TD.CONT: this should be cleaner
// TO.DOCONT: this should be cleaner
setTimeout(() => {
cgpv.api.createMapFromConfig('sandboxMap', document.getElementById('configGeoview').value);
}, 1500);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import { createGuideObject } from '@/core/utils/utilities';
import { MapViewer } from '@/geo/map/map-viewer';
import { MapEventProcessor } from './map-event-processor';

// GV Important: See notes in header of MapEventProcessor file for information on the paradigm to apply when working with UIEventProcessor vs UIState

export class AppEventProcessor extends AbstractEventProcessor {
// Static functions for Typescript files to access store actions
// **********************************************************
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,7 @@ import { TypeLayerData } from '@/geo/layer/layer-sets/abstract-layer-set';
import { TypeAllFeatureInfoResultSet } from '@/geo/layer/layer-sets/all-feature-info-layer-set';
import { MapEventProcessor } from './map-event-processor';

// GV The paradigm when working with DataTableEventProcessor vs DataTableState goes like this:
// GV DataTableState provides: 'state values', 'actions' and 'setterActions'.
// GV Whereas Zustand would suggest having 'state values' and 'actions', in GeoView, we have a 'DataTableEventProcessor' in the middle.
// GV This is because we wanted to have centralized code between UI actions and backend actions via a DataTableEventProcessor.
// GV In summary:
// GV The UI components should use DataTableState's 'state values' to read and 'actions' to set states (which simply redirect to DataTableEventProcessor).
// GV The back-end code should use DataTableEventProcessor which uses 'state values' and 'setterActions'
// GV Essentially 3 main call-stacks:
// GV - DataTableEventProcessor ---calls---> DataTableState.setterActions
// GV - UI Component ---calls---> DataTableState.actions ---calls---> DataTableEventProcessor ---calls---> DataTableState.setterActions
// GV - DataTableEventProcessor ---triggers---> DataTableViewer events ---calls---> DataTableState.setterActions
// GV The reason for this pattern is so that UI components and processes performing back-end code
// GV both end up running code in DataTableEventProcessor (UI: via 'actions' and back-end code via 'DataTableEventProcessor')
// GV Important: See notes in header of MapEventProcessor file for information on the paradigm to apply when working with UIEventProcessor vs UIState

export class DataTableEventProcessor extends AbstractEventProcessor {
// **********************************************************
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import { EventType, TypeLayerData } from '@/geo/layer/layer-sets/abstract-layer-
import { AbstractEventProcessor, BatchedPropagationLayerDataArrayByMap } from '@/api/event-processors/abstract-event-processor';
import { UIEventProcessor } from './ui-event-processor';

// GV Important: See notes in header of MapEventProcessor file for information on the paradigm to apply when working with UIEventProcessor vs UIState

/**
* Event processor focusing on interacting with the feature info state in the store (currently called detailsState).
*/
Expand Down Expand Up @@ -77,7 +79,7 @@ export class FeatureInfoEventProcessor extends AbstractEventProcessor {
// Redirect to helper function
this.#deleteFromArray(featureInfoState.layerDataArray, layerPath, (layerArrayResult) => {
// Update the layer data array in the store
featureInfoState.actions.setLayerDataArray(layerArrayResult);
featureInfoState.setterActions.setLayerDataArray(layerArrayResult);

// Log
logger.logInfo('Removed Feature Info in stores for layer path:', layerPath);
Expand Down Expand Up @@ -135,7 +137,7 @@ export class FeatureInfoEventProcessor extends AbstractEventProcessor {
const atLeastOneFeature = layerDataArray.find((layerEntry) => !!layerEntry.features?.length) || false;

// Update the layer data array in the store, all the time, for all statuses
featureInfoState.actions.setLayerDataArray(layerDataArray);
featureInfoState.setterActions.setLayerDataArray(layerDataArray);

// If there was some features on this propagation
if (atLeastOneFeature) {
Expand Down Expand Up @@ -175,10 +177,10 @@ export class FeatureInfoEventProcessor extends AbstractEventProcessor {
layerDataArray,
this.#batchedPropagationLayerDataArray,
this.#timeDelayBetweenPropagationsForBatch,
featureInfoState.actions.setLayerDataArrayBatch,
featureInfoState.setterActions.setLayerDataArrayBatch,
'feature-info-processor',
featureInfoState.layerDataArrayBatchLayerPathBypass,
featureInfoState.actions.setLayerDataArrayBatchLayerPathBypass
featureInfoState.setterActions.setLayerDataArrayBatchLayerPathBypass
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import { TypeLayerData } from '@/geo/layer/layer-sets/abstract-layer-set';

import { AbstractEventProcessor, BatchedPropagationLayerDataArrayByMap } from '@/api/event-processors/abstract-event-processor';

// GV Important: See notes in header of MapEventProcessor file for information on the paradigm to apply when working with UIEventProcessor vs UIState

/**
* Event processor focusing on interacting with the geochart state in the store.
*/
Expand Down Expand Up @@ -102,7 +104,7 @@ export class GeochartEventProcessor extends AbstractEventProcessor {
});

// set store charts config
this.getGeochartState(mapId)?.actions.setGeochartCharts(chartData);
this.getGeochartState(mapId)?.setterActions.setGeochartCharts(chartData);

// Log
logger.logInfo('Added GeoChart configs for layer paths:', layerPaths);
Expand All @@ -124,7 +126,7 @@ export class GeochartEventProcessor extends AbstractEventProcessor {
toAdd[layerPath] = chartConfig;

// Update the layer data array in the store
this.getGeochartState(mapId)!.actions.setGeochartCharts({ ...this.getGeochartState(mapId)?.geochartChartsConfig, ...toAdd });
this.getGeochartState(mapId)!.setterActions.setGeochartCharts({ ...this.getGeochartState(mapId)?.geochartChartsConfig, ...toAdd });

// Log
logger.logInfo('Added GeoChart configs for layer path:', layerPath);
Expand All @@ -150,7 +152,7 @@ export class GeochartEventProcessor extends AbstractEventProcessor {
delete chartConfigs[layerPath];

// Update the layer data array in the store
this.getGeochartState(mapId)!.actions.setGeochartCharts({ ...chartConfigs });
this.getGeochartState(mapId)!.setterActions.setGeochartCharts({ ...chartConfigs });

// Log
logger.logInfo('Removed GeoChart configs for layer path:', layerPath);
Expand All @@ -170,7 +172,7 @@ export class GeochartEventProcessor extends AbstractEventProcessor {
if (!this.getGeochartState(mapId)) return;

// Update the layer data array in the store
this.getGeochartState(mapId)!.actions.setLayerDataArray(layerDataArray);
this.getGeochartState(mapId)!.setterActions.setLayerDataArray(layerDataArray);
}

/**
Expand Down Expand Up @@ -198,10 +200,10 @@ export class GeochartEventProcessor extends AbstractEventProcessor {
layerDataArray,
this.batchedPropagationLayerDataArray,
this.timeDelayBetweenPropagationsForBatch,
geochartState.actions.setLayerDataArrayBatch,
geochartState.setterActions.setLayerDataArrayBatch,
'geochart-processor',
geochartState.layerDataArrayBatchLayerPathBypass,
geochartState.actions.setLayerDataArrayBatchLayerPathBypass
geochartState.setterActions.setLayerDataArrayBatchLayerPathBypass
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ import {
import { AppEventProcessor } from './app-event-processor';
import { MapEventProcessor } from './map-event-processor';

// GV Important: See notes in header of MapEventProcessor file for information on the paradigm to apply when working with UIEventProcessor vs UIState

export class LegendEventProcessor extends AbstractEventProcessor {
// **********************************************************
// Static functions for Typescript files to access store actions
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ export class MapEventProcessor extends AbstractEventProcessor {
try {
// Check if the plugins exist
// TODO: if you run the code fast enough (only happened to me in the TimeSliderEventProcessor),
// TD.CONT: the getMapViewer should be async, because it can be unset as well ( so not just getMapViewerPlugins() ).
// TO.DOCONT: the getMapViewer should be async, because it can be unset as well ( so not just getMapViewerPlugins() ).
await whenThisThen(() => api && api.maps && api.maps[mapId] && api.maps[mapId].plugins);
} catch (error) {
// Log
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { logger } from '@/core/utils/logger';

import { AbstractEventProcessor } from '@/api/event-processors/abstract-event-processor';

// GV Important: See notes in header of MapEventProcessor file for information on the paradigm to apply when working with UIEventProcessor vs UIState

/**
* Event processor focusing on interacting with the swiper state in the store.
*/
Expand Down Expand Up @@ -34,7 +36,7 @@ export class SwiperEventProcessor extends AbstractEventProcessor {
*/
static setLayerPaths(mapId: string, layerPaths: string[]): void {
// set store layer paths
this.getSwiperState(mapId)?.actions.setLayerPaths(layerPaths);
this.getSwiperState(mapId)?.setterActions.setLayerPaths(layerPaths);

// Log
logger.logInfo('Added Swiper functionality for layer paths:', layerPaths);
Expand All @@ -60,7 +62,7 @@ export class SwiperEventProcessor extends AbstractEventProcessor {
updatedArray.push(layerPath);

// Update the layer data array in the store
this.getSwiperState(mapId)!.actions.setLayerPaths(updatedArray);
this.getSwiperState(mapId)!.setterActions.setLayerPaths(updatedArray);

// Log
logger.logInfo('Added Swiper functionality for layer path:', layerPath);
Expand Down Expand Up @@ -93,7 +95,7 @@ export class SwiperEventProcessor extends AbstractEventProcessor {
updatedArray.splice(layerIndex, 1);

// Update the layer data array in the store
this.getSwiperState(mapId)!.actions.setLayerPaths(updatedArray);
this.getSwiperState(mapId)!.setterActions.setLayerPaths(updatedArray);

// Log
logger.logInfo('Removed Swiper functionality for layer path:', layerPath);
Expand All @@ -119,7 +121,7 @@ export class SwiperEventProcessor extends AbstractEventProcessor {
const { layerPaths } = this.getSwiperState(mapId)!;

// Update the layer data array in the store
this.getSwiperState(mapId)!.actions.setLayerPaths([]);
this.getSwiperState(mapId)!.setterActions.setLayerPaths([]);

// Log
logger.logInfo('Removed Swiper functionality for all layer paths', layerPaths);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@ import { AbstractEventProcessor } from '@/api/event-processors/abstract-event-pr
import { AbstractGeoViewVector } from '@/geo/layer/geoview-layers/vector/abstract-geoview-vector';
import { ITimeSliderState, TypeTimeSliderValues } from '@/core/stores/store-interface-and-intial-values/time-slider-state';
import { getLocalizedValue } from '@/core/utils/utilities';
import { AbstractGeoViewLayer, CONST_LAYER_TYPES } from '@/geo/layer/geoview-layers/abstract-geoview-layers';
import { CONST_LAYER_TYPES } from '@/geo/layer/geoview-layers/abstract-geoview-layers';
import { EsriDynamic } from '@/geo/layer/geoview-layers/raster/esri-dynamic';
import { WMS } from '@/geo/layer/geoview-layers/raster/wms';
import { TypeFeatureInfoLayerConfig, TypeLayerEntryConfig } from '@/geo/map/map-schema-types';
import { EsriImage } from '@/geo/layer/geoview-layers/raster/esri-image';
import { AppEventProcessor } from './app-event-processor';
import { MapEventProcessor } from './map-event-processor';

// GV Important: See notes in header of MapEventProcessor file for information on the paradigm to apply when working with UIEventProcessor vs UIState

export class TimeSliderEventProcessor extends AbstractEventProcessor {
// **********************************************************
Expand All @@ -33,51 +36,40 @@ export class TimeSliderEventProcessor extends AbstractEventProcessor {
/**
* Checks if the layer has time slider values. If there are, adds the time slider layer and applies filters.
* @param {string} mapId - The map id of the state to act on
* @param {AbstractGeoViewLayer} geoviewLayer - The GeoView layer to act on
* @param {TypeLayerEntryConfig} layerConfig - The layer path of the layer to add to the state
*/
static checkInitTimeSliderLayerAndApplyFilters(
mapId: string,
geoviewLayer: AbstractGeoViewLayer,
layerConfig: TypeLayerEntryConfig
): void {
static checkInitTimeSliderLayerAndApplyFilters(mapId: string, layerConfig: TypeLayerEntryConfig): void {
// If there is no TimeSlider
if (!this.getTimesliderState(mapId)) return;

// Get the time slider values
const timeSliderValues = this.getInitialTimeSliderValues(mapId, geoviewLayer, layerConfig);
const timeSliderValues = this.getInitialTimeSliderValues(mapId, layerConfig);

// If any
if (timeSliderValues) {
// Add the time slider in store
this.addTimeSliderLayerAndApplyFilters(mapId, geoviewLayer, layerConfig.layerPath, timeSliderValues);
this.addTimeSliderLayerAndApplyFilters(mapId, layerConfig.layerPath, timeSliderValues);
}
}

/**
* Adds a time slider layer to the state
* @param {string} mapId - The map id of the state to act on
* @param {AbstractGeoViewLayer} geoviewLayer - The GeoView layer to act on
* @param {string} layerPath - The layer path of the layer to add to the state
* @param {TypeTimeSliderValues} timeSliderValues - The time slider values to add and apply filters
*/
static addTimeSliderLayerAndApplyFilters(
mapId: string,
geoviewLayer: AbstractGeoViewLayer,
layerPath: string,
timeSliderValues: TypeTimeSliderValues
): void {
static addTimeSliderLayerAndApplyFilters(mapId: string, layerPath: string, timeSliderValues: TypeTimeSliderValues): void {
// If there is no TimeSlider
if (!this.getTimesliderState(mapId)) return;

// Create set part (because that's how it works for now)
const timeSliderLayer = { [layerPath]: timeSliderValues };

// Add it
this.getTimesliderState(mapId)?.actions.addTimeSliderLayer(timeSliderLayer);
this.getTimesliderState(mapId)?.setterActions.addTimeSliderLayer(timeSliderLayer);

const { defaultValue, field, filtering, minAndMax, values } = timeSliderLayer[layerPath];
this.applyFilters(geoviewLayer, layerPath, defaultValue, field, filtering, minAndMax, values);
this.applyFilters(mapId, layerPath, defaultValue, field, filtering, minAndMax, values);
}

/**
Expand All @@ -87,25 +79,27 @@ export class TimeSliderEventProcessor extends AbstractEventProcessor {
*/
static removeTimeSliderLayer(mapId: string, layerPath: string): void {
// Redirect
this.getTimesliderState(mapId)?.actions.removeTimeSliderLayer(layerPath);
this.getTimesliderState(mapId)?.setterActions.removeTimeSliderLayer(layerPath);
}

/**
* Get initial values for a layer's time slider states
*
* @param {string} mapId - The id of the map
* @param {AbstractGeoViewLayer} geoviewLayer - The GeoView layer to act on
* @param {TypeLayerEntryConfig} layerConfig - The layer path of the layer to add to the state
* @returns {TimeSliderLayer | undefined}
*/
static getInitialTimeSliderValues(
mapId: string,
geoviewLayer: AbstractGeoViewLayer,
layerConfig: TypeLayerEntryConfig
): TypeTimeSliderValues | undefined {
const name = getLocalizedValue(layerConfig.layerName, AppEventProcessor.getDisplayLanguage(mapId)) || layerConfig.layerId;
static getInitialTimeSliderValues(mapId: string, layerConfig: TypeLayerEntryConfig): TypeTimeSliderValues | undefined {
// Get the layer using the map event processor, If no temporal dimension OR layerPath, return undefined
if (!layerConfig.layerPath) return undefined;
const geoviewLayer = MapEventProcessor.getMapViewerLayerAPI(mapId).getGeoviewLayer(layerConfig.layerPath)!;
const temporalDimensionInfo = geoviewLayer.getTemporalDimension(layerConfig.layerPath);
if (!temporalDimensionInfo || !temporalDimensionInfo.range) return undefined;

// Get layer name and temporal dimension
const name = getLocalizedValue(layerConfig.layerName, AppEventProcessor.getDisplayLanguage(mapId)) || layerConfig.layerId;

// Set defaults values from temporal dimension
const { range } = temporalDimensionInfo.range;
const defaultValueIsArray = Array.isArray(temporalDimensionInfo.default);
const defaultValue = defaultValueIsArray ? temporalDimensionInfo.default[0] : temporalDimensionInfo.default;
Expand Down Expand Up @@ -161,7 +155,7 @@ export class TimeSliderEventProcessor extends AbstractEventProcessor {
/**
* Filter the layer provided in the layerPath variable according to current states (filtering and values)
*
* @param {AbstractGeoViewLayer} geoviewLayer - The GeoView layer to act on
* @param {string} mapId - The id of the map
* @param {string} layerPath - The path of the layer to filter
* @param {string} defaultValue - The default value to use if filters are off
* @param {string} field - The field to filter the layer by
Expand All @@ -171,14 +165,17 @@ export class TimeSliderEventProcessor extends AbstractEventProcessor {
* @returns {void}
*/
static applyFilters(
geoviewLayer: AbstractGeoViewLayer,
mapId: string,
layerPath: string,
defaultValue: string,
field: string,
filtering: boolean,
minAndMax: number[],
values: number[]
): void {
// Get the layer using the map event processor
const geoviewLayer = MapEventProcessor.getMapViewerLayerAPI(mapId).getGeoviewLayer(layerPath)!;

const layerType = geoviewLayer.type;
if (layerType === CONST_LAYER_TYPES.WMS) {
if (filtering) {
Expand Down Expand Up @@ -210,6 +207,9 @@ export class TimeSliderEventProcessor extends AbstractEventProcessor {
}
(geoviewLayer as AbstractGeoViewVector | EsriDynamic).applyViewFilter(layerPath, filter);
}

this.getTimesliderState(mapId)?.setterActions.setFiltering(layerPath, filtering);
this.getTimesliderState(mapId)?.setterActions.setValues(layerPath, values);
}
// #endregion
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,7 @@ import { TypeMapCorePackages, TypeValidAppBarCoreProps } from '@config/types/map
import { AbstractEventProcessor } from '@/api/event-processors/abstract-event-processor';
import { IUIState } from '@/core/stores/store-interface-and-intial-values/ui-state';

// GV The paradigm when working with UIEventProcessor vs UIState goes like this:
// GV UIState provides: 'state values', 'actions' and 'setterActions'.
// GV Whereas Zustand would suggest having 'state values' and 'actions', in GeoView, we have a 'UIEventProcessor' in the middle.
// GV This is because we wanted to have centralized code between UI actions and backend actions via a UIEventProcessor.
// GV In summary:
// GV The UI components should use UIState's 'state values' to read and 'actions' to set states (which simply redirect to UIEventProcessor).
// GV The back-end code should use UIEventProcessor which uses 'state values' and 'setterActions'
// GV Essentially 3 main call-stacks:
// GV - UIEventProcessor ---calls---> UIState.setterActions
// GV - UI Component ---calls---> UIState.actions ---calls---> UIEventProcessor ---calls---> UIState.setterActions
// GV - UIEventProcessor ---triggers---> UIViewer events ---calls---> UIState.setterActions
// GV The reason for this pattern is so that UI components and processes performing back-end code
// GV both end up running code in UIEventProcessor (UI: via 'actions' and back-end code via 'UIEventProcessor')
// GV Important: See notes in header of MapEventProcessor file for information on the paradigm to apply when working with UIEventProcessor vs UIState

export class UIEventProcessor extends AbstractEventProcessor {
// **********************************************************
Expand Down
Loading

0 comments on commit 75daf6b

Please sign in to comment.