Skip to content

Commit

Permalink
refactor(state): use the setterAction paradigm
Browse files Browse the repository at this point in the history
Closes #2148
  • Loading branch information
jolevesq committed May 16, 2024
1 parent 63c6bbc commit c76cf4f
Show file tree
Hide file tree
Showing 17 changed files with 235 additions and 82 deletions.
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 @@ -9,19 +9,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
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
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 @@ -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 @@ -9,6 +9,8 @@ import { TypeFeatureInfoLayerConfig, TypeLayerEntryConfig } from '@/geo/map/map-
import { EsriImage } from '@/geo/layer/geoview-layers/raster/esri-image';
import { AppEventProcessor } from './app-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 {
// **********************************************************
// Static functions for Typescript files to access store actions
Expand Down Expand Up @@ -74,7 +76,7 @@ export class TimeSliderEventProcessor extends AbstractEventProcessor {
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);
Expand All @@ -87,7 +89,7 @@ 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);
}

/**
Expand Down
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
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ import { NotificationDetailsType } from '@/core/components/notifications/notific
import { TypeHTMLElement, TypeMapFeaturesConfig } from '@/core/types/global-types';
import { logger } from '@/core/utils/logger';

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

// #region INTERFACES & TYPES

type AppActions = IAppState['actions'];

export interface IAppState {
displayLanguage: TypeDisplayLanguage;
displayTheme: TypeDisplayTheme;
Expand Down Expand Up @@ -40,11 +46,13 @@ export interface IAppState {
};
}

// #endregion INTERFACES & TYPES

/**
* Initializes an App State and provide functions which use the get/set Zustand mechanisms.
* @param {TypeSetStore} set - The setter callback to be used by this state
* @param {TypeGetStore} get - The getter callback to be used by this state
* @returns The initialized Map State
* @returns {IAppState} - The initialized App State
*/
export function initializeAppState(set: TypeSetStore, get: TypeGetStore): IAppState {
return {
Expand Down Expand Up @@ -72,6 +80,7 @@ export function initializeAppState(set: TypeSetStore, get: TypeGetStore): IAppSt
},

// #region ACTIONS

actions: {
/**
* Adds a notification.
Expand Down Expand Up @@ -132,9 +141,7 @@ export function initializeAppState(set: TypeSetStore, get: TypeGetStore): IAppSt
AppEventProcessor.removeNotification(get().mapId, key);
},
},
// #endregion ACTIONS

// #region SETTER ACTIONS
setterActions: {
/**
* Sets the circularProgress state.
Expand Down Expand Up @@ -232,7 +239,8 @@ export function initializeAppState(set: TypeSetStore, get: TypeGetStore): IAppSt
});
},
},
// #endregion SETTER ACTIONS

// #endregion ACTIONS
} as IAppState;
}

Expand All @@ -258,13 +266,11 @@ export const useAppGeoviewHTMLElement = (): HTMLElement => useStore(useGeoViewSt
export const useAppGuide = (): TypeGuideObject | undefined => useStore(useGeoViewStore(), (state) => state.appState.guide);
export const useAppNotifications = (): NotificationDetailsType[] => useStore(useGeoViewStore(), (state) => state.appState.notifications);

// GV these 2 selector are use in app-start.tsx before context is assigned to the map
// GV these 2 selectors are use in app-start.tsx before context is assigned to the map
// GV DO NOT USE this technique elsewhere, it is only to reload language and theme
export const useAppDisplayLanguageById = (mapId: string): TypeDisplayLanguage =>
useStore(getGeoViewStore(mapId), (state) => state.appState.displayLanguage);
export const useAppDisplayThemeById = (mapId: string): TypeDisplayTheme =>
useStore(getGeoViewStore(mapId), (state) => state.appState.displayTheme);

// TODO: Refactor - We should explicit a type for the appState.actions
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export const useAppStoreActions = (): any => useStore(useGeoViewStore(), (state) => state.appState.actions);
export const useAppStoreActions = (): AppActions => useStore(useGeoViewStore(), (state) => state.appState.actions);
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@ import { TypeFeatureInfoEntry, TypeLayerData } from '@/geo/layer/layer-sets/abst
import { TypeSetStore, TypeGetStore } from '@/core/stores/geoview-store';
import { useGeoViewStore } from '@/core/stores/stores-managers';

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

// #region INTERFACES & TYPES

type DataTableActions = IDataTableState['actions'];

// Import { MRTColumnFiltersState } from 'material-react-table' fails - This is likely not portable. a type annotation is necessary
// Create a type to mimic
Expand Down Expand Up @@ -60,6 +64,14 @@ export interface IDataTableState {
};
}

// #endregion INTERFACES & TYPES

/**
* Initializes an DataTable State and provide functions which use the get/set Zustand mechanisms.
* @param {TypeSetStore} set - The setter callback to be used by this state
* @param {TypeGetStore} get - The getter callback to be used by this state
* @returns {IDataTableState} - The initialized DataTable State
*/
export function initialDataTableState(set: TypeSetStore, get: TypeGetStore): IDataTableState {
return {
activeLayerData: [],
Expand All @@ -69,6 +81,8 @@ export function initialDataTableState(set: TypeSetStore, get: TypeGetStore): IDa
tableHeight: 600,
selectedFeature: null,

// #region ACTIONS

actions: {
applyMapFilters: (filterStrings: string): void => {
const layerPath = get().dataTableState.selectedLayerPath;
Expand Down Expand Up @@ -234,6 +248,8 @@ export function initialDataTableState(set: TypeSetStore, get: TypeGetStore): IDa
});
},
},

// #endregion ACTIONS
} as IDataTableState;
}

Expand All @@ -249,6 +265,4 @@ export const useDataTableTableHeight = (): number => useStore(useGeoViewStore(),
export const useDataTableSelectedFeature = (): TypeFeatureInfoEntry | null =>
useStore(useGeoViewStore(), (state) => state.dataTableState.selectedFeature);

// TODO: Refactor - We should explicit a type for the dataTableState.actions
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export const useDataTableStoreActions = (): any => useStore(useGeoViewStore(), (state) => state.dataTableState.actions);
export const useDataTableStoreActions = (): DataTableActions => useStore(useGeoViewStore(), (state) => state.dataTableState.actions);
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@ import { TypeSetStore, TypeGetStore } from '@/core/stores/geoview-store';
import { useGeoViewStore } from '@/core/stores/stores-managers';
import { TypeLayerData, TypeFeatureInfoEntry, TypeGeometry } from '@/geo/layer/layer-sets/abstract-layer-set';

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

// #region INTERFACES & TYPES

type FeatureInfoActions = IFeatureInfoState['actions'];

export interface IFeatureInfoState {
checkedFeatures: Array<TypeFeatureInfoEntry>;
layerDataArray: TypeLayerData[];
Expand All @@ -18,8 +24,25 @@ export interface IFeatureInfoState {
setLayerDataArrayBatchLayerPathBypass: (layerPath: string) => void;
setSelectedLayerPath: (selectedLayerPath: string) => void;
};

setterActions: {
addCheckedFeature: (feature: TypeFeatureInfoEntry) => void;
removeCheckedFeature: (feature: TypeFeatureInfoEntry | 'all') => void;
setLayerDataArray: (layerDataArray: TypeLayerData[]) => void;
setLayerDataArrayBatch: (layerDataArray: TypeLayerData[]) => void;
setLayerDataArrayBatchLayerPathBypass: (layerPath: string) => void;
setSelectedLayerPath: (selectedLayerPath: string) => void;
};
}

// #endregion INTERFACES & TYPES

/**
* Initializes an FeatureInfo State and provide functions which use the get/set Zustand mechanisms.
* @param {TypeSetStore} set - The setter callback to be used by this state
* @param {TypeGetStore} get - The getter callback to be used by this state
* @returns {IFeatureInfoState} - The initialized FeatureInfo State
*/
export function initFeatureInfoState(set: TypeSetStore, get: TypeGetStore): IFeatureInfoState {
return {
checkedFeatures: [],
Expand All @@ -29,7 +52,35 @@ export function initFeatureInfoState(set: TypeSetStore, get: TypeGetStore): IFea
selectedLayerPath: '',

// #region ACTIONS

actions: {
addCheckedFeature: (feature: TypeFeatureInfoEntry) => {
// Redirect to setter
get().detailsState.setterActions.addCheckedFeature(feature);
},
removeCheckedFeature: (feature: TypeFeatureInfoEntry | 'all') => {
// Redirect to setter
get().detailsState.setterActions.removeCheckedFeature(feature);
},
setLayerDataArray(layerDataArray: TypeLayerData[]) {
// Redirect to setter
get().detailsState.setterActions.setLayerDataArray(layerDataArray);
},
setLayerDataArrayBatch(layerDataArrayBatch: TypeLayerData[]) {
// Redirect to setter
get().detailsState.setterActions.setLayerDataArrayBatch(layerDataArrayBatch);
},
setLayerDataArrayBatchLayerPathBypass(layerDataArrayBatchLayerPathBypass: string) {
// Redirect to setter
get().detailsState.setterActions.setLayerDataArrayBatchLayerPathBypass(layerDataArrayBatchLayerPathBypass);
},
setSelectedLayerPath(selectedLayerPath: string) {
// Redirect to setter
get().detailsState.setterActions.setSelectedLayerPath(selectedLayerPath);
},
},

setterActions: {
addCheckedFeature: (feature: TypeFeatureInfoEntry) => {
set({
detailsState: {
Expand Down Expand Up @@ -85,6 +136,7 @@ export function initFeatureInfoState(set: TypeSetStore, get: TypeGetStore): IFea
});
},
},

// #endregion ACTIONS
} as IFeatureInfoState;
}
Expand All @@ -99,6 +151,4 @@ export const useDetailsLayerDataArrayBatch = (): TypeLayerData[] =>
useStore(useGeoViewStore(), (state) => state.detailsState.layerDataArrayBatch);
export const useDetailsSelectedLayerPath = (): string => useStore(useGeoViewStore(), (state) => state.detailsState.selectedLayerPath);

// TODO: Refactor - We should explicit a type for the featureInfoState.actions
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export const useDetailsStoreActions = (): any => useStore(useGeoViewStore(), (state) => state.detailsState.actions);
export const useDetailsStoreActions = (): FeatureInfoActions => useStore(useGeoViewStore(), (state) => state.detailsState.actions);
Loading

0 comments on commit c76cf4f

Please sign in to comment.