From e4a8e739c3f8b471f33044e1b74435b6ef394d5a Mon Sep 17 00:00:00 2001 From: Damon Ulmi <63123585+DamonU2@users.noreply.github.com> Date: Thu, 18 Jul 2024 09:50:41 -0700 Subject: [PATCH] feat(plugins): Hides plugin tabs if no layers support them (#2384) Closes #2369 --- .github/workflows/type-build.yml | 3 +- .../geochart-event-processor.ts | 12 ++++- .../map-event-processor.ts | 15 +++++-- .../time-slider-event-processor.ts | 8 ++++ .../ui-event-processor.ts | 36 ++++++++++++--- .../core/components/footer-bar/footer-bar.tsx | 4 +- .../geoview-core/src/core/stores/state-api.ts | 2 +- .../ui-state.ts | 23 ++++++++++ packages/geoview-core/src/geo/layer/layer.ts | 12 ++--- .../geoview-core/src/geo/map/map-viewer.ts | 2 +- .../geoview-core/src/ui/button/button.tsx | 13 +++++- packages/geoview-core/src/ui/tabs/tabs.tsx | 4 +- .../geoview-time-slider/src/time-slider.tsx | 44 +++++++++++-------- 13 files changed, 134 insertions(+), 44 deletions(-) diff --git a/.github/workflows/type-build.yml b/.github/workflows/type-build.yml index f3149f440d7..e28dd0cfc0b 100644 --- a/.github/workflows/type-build.yml +++ b/.github/workflows/type-build.yml @@ -44,7 +44,8 @@ jobs: rm -r common rm -r packages - git config --global user.name $USER + git config --global user.email "damon.ulmi@nrcan-rncan.gc.ca" + git config --global user.name "Damon Ulmi" git add . git status 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 4601daead7e..24a3ff6e0cc 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 @@ -8,6 +8,7 @@ import { GeoChartConfig } from '@/core/utils/config/reader/uuid-config-reader'; import { logger } from '@/core/utils/logger'; 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 @@ -120,9 +121,12 @@ export class GeochartEventProcessor extends AbstractEventProcessor { }); }); - // set store charts config + // Set store charts config this.getGeochartState(mapId)?.setterActions.setGeochartCharts(chartData); + // If there is chart data, tab should not be hidden + if (Object.keys(chartData).length) UIEventProcessor.showTab(mapId, 'geochart'); + // Log logger.logInfo('Added GeoChart configs for layer paths:', layerPaths); } @@ -145,6 +149,9 @@ export class GeochartEventProcessor extends AbstractEventProcessor { // Update the layer data array in the store this.getGeochartState(mapId)!.setterActions.setGeochartCharts({ ...this.getGeochartState(mapId)?.geochartChartsConfig, ...toAdd }); + // Make sure tab is not hidden + UIEventProcessor.showTab(mapId, 'geochart'); + // Log logger.logInfo('Added GeoChart configs for layer path:', layerPath); } @@ -171,6 +178,9 @@ export class GeochartEventProcessor extends AbstractEventProcessor { // Update the layer data array in the store this.getGeochartState(mapId)!.setterActions.setGeochartCharts({ ...chartConfigs }); + // If there are no more geochart layers, hide tab + if (!Object.keys(this.getGeochartState(mapId)!.geochartChartsConfig).length) UIEventProcessor.hideTab(mapId, 'geochart'); + // Log logger.logInfo('Removed GeoChart configs for layer path:', layerPath); } diff --git a/packages/geoview-core/src/api/event-processors/event-processor-children/map-event-processor.ts b/packages/geoview-core/src/api/event-processors/event-processor-children/map-event-processor.ts index edc37b2558c..481e3ec2e5c 100644 --- a/packages/geoview-core/src/api/event-processors/event-processor-children/map-event-processor.ts +++ b/packages/geoview-core/src/api/event-processors/event-processor-children/map-event-processor.ts @@ -303,12 +303,14 @@ export class MapEventProcessor extends AbstractEventProcessor { } /** - * Gets the ordered layer info. + * Gets map layer paths in order. * @param {string} mapId - The map id - * @returns {TypeOrderedLayerInfo[]} The ordered layer info + * @returns {string[]} The ordered layer paths */ - static getMapLayerOrder(mapId: string): TypeOrderedLayerInfo[] { - return this.getMapStateProtected(mapId).orderedLayerInfo; + static getMapLayerOrder(mapId: string): string[] { + return this.getMapStateProtected(mapId).orderedLayerInfo.map((orderedLayerInfo) => { + return orderedLayerInfo.layerPath; + }); } static getMapState(mapId: string): TypeMapState { @@ -453,6 +455,11 @@ export class MapEventProcessor extends AbstractEventProcessor { // GV No need to save in the store, because this will trigger an event on MapViewer which will take care of updating the store } + /** + * Gets the ordered layer info. + * @param {string} mapId - The map id + * @returns {TypeOrderedLayerInfo[]} The ordered layer info + */ static getMapOrderedLayerInfo(mapId: string): TypeOrderedLayerInfo[] { return this.getMapStateProtected(mapId).orderedLayerInfo; } diff --git a/packages/geoview-core/src/api/event-processors/event-processor-children/time-slider-event-processor.ts b/packages/geoview-core/src/api/event-processors/event-processor-children/time-slider-event-processor.ts index 90f7634e349..ab0868c65d1 100644 --- a/packages/geoview-core/src/api/event-processors/event-processor-children/time-slider-event-processor.ts +++ b/packages/geoview-core/src/api/event-processors/event-processor-children/time-slider-event-processor.ts @@ -15,6 +15,7 @@ import { MapEventProcessor } from './map-event-processor'; import { AbstractGVVector } from '@/geo/layer/gv-layers/vector/abstract-gv-vector'; import { GVWMS } from '@/geo/layer/gv-layers/raster/gv-wms'; import { GVEsriImage } from '@/geo/layer/gv-layers/raster/gv-esri-image'; +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 @@ -85,6 +86,9 @@ export class TimeSliderEventProcessor extends AbstractEventProcessor { const { defaultValue, field, filtering, minAndMax, values } = timeSliderLayer[layerPath]; this.applyFilters(mapId, layerPath, defaultValue, field, filtering, minAndMax, values); + + // Make sure tab is visible + UIEventProcessor.showTab(mapId, 'time-slider'); } /** @@ -95,6 +99,10 @@ export class TimeSliderEventProcessor extends AbstractEventProcessor { static removeTimeSliderLayer(mapId: string, layerPath: string): void { // Redirect this.getTimesliderState(mapId)?.setterActions.removeTimeSliderLayer(layerPath); + + // If there are no layers with time dimension, hide tab + if (!this.getTimesliderState(mapId) || !Object.keys(this.getTimesliderState(mapId)!.timeSliderLayers).length) + UIEventProcessor.hideTab(mapId, 'time-slider'); } /** diff --git a/packages/geoview-core/src/api/event-processors/event-processor-children/ui-event-processor.ts b/packages/geoview-core/src/api/event-processors/event-processor-children/ui-event-processor.ts index dfa25c79ae2..798bf94cc63 100644 --- a/packages/geoview-core/src/api/event-processors/event-processor-children/ui-event-processor.ts +++ b/packages/geoview-core/src/api/event-processors/event-processor-children/ui-event-processor.ts @@ -17,22 +17,26 @@ export class UIEventProcessor extends AbstractEventProcessor { * @param {string} mapId The mapId * @returns {IUIState} The UI state. */ - protected static getUIState(mapId: string): IUIState { + protected static getUIStateProtected(mapId: string): IUIState { // Return the time slider state return super.getState(mapId).uiState; } // #region static getActiveFooterBarTab(mapId: string): string { - return this.getUIState(mapId).activeFooterBarTabId; + return this.getUIStateProtected(mapId).activeFooterBarTabId; } static getAppBarComponents(mapId: string): TypeValidAppBarCoreProps[] { - return this.getUIState(mapId).appBarComponents; + return this.getUIStateProtected(mapId).appBarComponents; } static getCorePackageComponents(mapId: string): TypeMapCorePackages { - return this.getUIState(mapId).corePackagesComponents; + return this.getUIStateProtected(mapId).corePackagesComponents; + } + + static getFooterBarIsCollapsed(mapId: string): boolean { + return this.getUIStateProtected(mapId).footerBarIsCollapsed; } // #endregion @@ -41,15 +45,33 @@ export class UIEventProcessor extends AbstractEventProcessor { // ********************************************************** // GV NEVER add a store action who does set state AND map action at a same time. // GV Review the action in store state to make sure + static hideTab(mapId: string, tab: string): void { + if (!this.getUIStateProtected(mapId).hiddenTabs.includes(tab)) + this.getUIStateProtected(mapId).setterActions.setHiddenTabs([...this.getUIStateProtected(mapId).hiddenTabs, tab]); + } + + static showTab(mapId: string, tab: string): void { + const curHiddenTabs = this.getUIStateProtected(mapId).hiddenTabs; + const tabIndex = curHiddenTabs.indexOf(tab); + if (tabIndex !== -1) { + curHiddenTabs.splice(tabIndex, 1); + this.getUIStateProtected(mapId).setterActions.setHiddenTabs(curHiddenTabs); + } + } + static setActiveFooterBarTab(mapId: string, id: string): void { - this.getUIState(mapId).setterActions.setActiveFooterBarTab(id); + this.getUIStateProtected(mapId).setterActions.setActiveFooterBarTab(id); } static setActiveAppBarTab(mapId: string, tabId: string, tabGroup: string, isOpen: boolean): void { - this.getUIState(mapId).setterActions.setActiveAppBarTab(tabId, tabGroup, isOpen); + this.getUIStateProtected(mapId).setterActions.setActiveAppBarTab(tabId, tabGroup, isOpen); } static getActiveAppBarTab(mapId: string): ActiveAppBarTabType { - return this.getUIState(mapId).activeAppBarTab; + return this.getUIStateProtected(mapId).activeAppBarTab; + } + + static setFooterBarIsCollapsed(mapId: string, collapsed: boolean): void { + this.getUIStateProtected(mapId).setterActions.setFooterBarIsCollapsed(collapsed); } } diff --git a/packages/geoview-core/src/core/components/footer-bar/footer-bar.tsx b/packages/geoview-core/src/core/components/footer-bar/footer-bar.tsx index 0e665e5eae4..4ab01d15318 100644 --- a/packages/geoview-core/src/core/components/footer-bar/footer-bar.tsx +++ b/packages/geoview-core/src/core/components/footer-bar/footer-bar.tsx @@ -119,8 +119,8 @@ export function FooterBar(props: FooterBarProps): JSX.Element | null { label: `${camelCase(tab)}.title`, icon: allTabs[tab]?.icon ?? '', content: allTabs[tab]?.content ?? '', - }; - }) as unknown as TypeTabs[]; + } as TypeTabs; + }); }, [memoTabs, tabsList]); /** diff --git a/packages/geoview-core/src/core/stores/state-api.ts b/packages/geoview-core/src/core/stores/state-api.ts index 935c34b046e..0ced1c749e9 100644 --- a/packages/geoview-core/src/core/stores/state-api.ts +++ b/packages/geoview-core/src/core/stores/state-api.ts @@ -110,7 +110,7 @@ export class StateApi { // Apply some ordering logic const direction = move < 0 ? -1 : 1; let absoluteMoves = Math.abs(move); - const orderedLayers = [...MapEventProcessor.getMapLayerOrder(this.mapId)]; + const orderedLayers = [...MapEventProcessor.getMapOrderedLayerInfo(this.mapId)]; let startingIndex = -1; for (let i = 0; i < orderedLayers.length; i++) if (orderedLayers[i].layerPath === layerPath) startingIndex = i; const layerInfo = orderedLayers[startingIndex]; diff --git a/packages/geoview-core/src/core/stores/store-interface-and-intial-values/ui-state.ts b/packages/geoview-core/src/core/stores/store-interface-and-intial-values/ui-state.ts index ce2f08e4fd9..af9a269ee55 100644 --- a/packages/geoview-core/src/core/stores/store-interface-and-intial-values/ui-state.ts +++ b/packages/geoview-core/src/core/stores/store-interface-and-intial-values/ui-state.ts @@ -3,6 +3,7 @@ import { TypeMapCorePackages, TypeNavBarProps, TypeValidAppBarCoreProps } from ' import { useGeoViewStore } from '@/core/stores/stores-managers'; import { TypeSetStore, TypeGetStore } from '@/core/stores/geoview-store'; import { TypeMapFeaturesConfig } from '@/core/types/global-types'; +import { UIEventProcessor } from '@/api/event-processors/event-processor-children/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 @@ -23,6 +24,7 @@ export interface IUIState { appBarComponents: TypeValidAppBarCoreProps[]; corePackagesComponents: TypeMapCorePackages; focusITem: FocusItemProps; + hiddenTabs: string[]; mapInfoExpanded: boolean; navBarComponents: TypeNavBarProps; footerPanelResizeValue: number; @@ -31,8 +33,10 @@ export interface IUIState { setDefaultConfigValues: (geoviewConfig: TypeMapFeaturesConfig) => void; actions: { + hideTab: (tab: string) => void; closeModal: () => void; openModal: (uiFocus: FocusItemProps) => void; + showTab: (tab: string) => void; setActiveFooterBarTab: (id: string) => void; setActiveAppBarTab: (tabId: string, tabGroup: string, isOpen: boolean) => void; setActiveTrapGeoView: (active: boolean) => void; @@ -48,6 +52,7 @@ export interface IUIState { setActiveAppBarTab: (tabId: string, tabGroup: string, isOpen: boolean) => void; setActiveTrapGeoView: (active: boolean) => void; setFooterPanelResizeValue: (value: number) => void; + setHiddenTabs: (hiddenTabs: string[]) => void; setMapInfoExpanded: (expanded: boolean) => void; setFooterBarIsCollapsed: (collapsed: boolean) => void; }; @@ -69,6 +74,7 @@ export function initializeUIState(set: TypeSetStore, get: TypeGetStore): IUIStat activeTrapGeoView: false, corePackagesComponents: [], focusITem: { activeElementId: false, callbackElementId: false }, + hiddenTabs: ['time-slider', 'geochart'], mapInfoExpanded: false, navBarComponents: [], footerPanelResizeValue: 35, @@ -90,6 +96,10 @@ export function initializeUIState(set: TypeSetStore, get: TypeGetStore): IUIStat // #region ACTIONS actions: { + hideTab: (tab: string): void => { + // Redirect to event processor + UIEventProcessor.hideTab(get().mapId, tab); + }, closeModal: () => { // Redirect to setter get().uiState.setterActions.closeModal(); @@ -98,6 +108,10 @@ export function initializeUIState(set: TypeSetStore, get: TypeGetStore): IUIStat // Redirect to setter get().uiState.setterActions.openModal(uiFocus); }, + showTab: (tab: string): void => { + // Redirect to event processor + UIEventProcessor.showTab(get().mapId, tab); + }, setActiveFooterBarTab: (id: string) => { // Redirect to setter get().uiState.setterActions.setActiveFooterBarTab(id); @@ -158,6 +172,14 @@ export function initializeUIState(set: TypeSetStore, get: TypeGetStore): IUIStat }, }); }, + setHiddenTabs: (hiddenTabs: string[]) => { + set({ + uiState: { + ...get().uiState, + hiddenTabs: [...hiddenTabs], + }, + }); + }, setFooterPanelResizeValue: (value) => { set({ uiState: { @@ -220,6 +242,7 @@ export const useUICorePackagesComponents = (): TypeMapCorePackages => useStore(useGeoViewStore(), (state) => state.uiState.corePackagesComponents); export const useUIFooterPanelResizeValue = (): number => useStore(useGeoViewStore(), (state) => state.uiState.footerPanelResizeValue); export const useUIFooterPanelResizeValues = (): number[] => useStore(useGeoViewStore(), (state) => state.uiState.footerPanelResizeValues); +export const useUIHiddenTabs = (): string[] => useStore(useGeoViewStore(), (state) => state.uiState.hiddenTabs); export const useUIMapInfoExpanded = (): boolean => useStore(useGeoViewStore(), (state) => state.uiState.mapInfoExpanded); export const useUINavbarComponents = (): TypeNavBarProps => useStore(useGeoViewStore(), (state) => state.uiState.navBarComponents); export const useUIFooterBarIsCollapsed = (): boolean => useStore(useGeoViewStore(), (state) => state.uiState.footerBarIsCollapsed); diff --git a/packages/geoview-core/src/geo/layer/layer.ts b/packages/geoview-core/src/geo/layer/layer.ts index 251289286f9..cdd0ed70b2f 100644 --- a/packages/geoview-core/src/geo/layer/layer.ts +++ b/packages/geoview-core/src/geo/layer/layer.ts @@ -1395,17 +1395,17 @@ export class LayerApi { */ setOrToggleLayerVisibility(layerPath: string, newValue?: boolean): void { // Apply some visibility logic - const curOrderedLayerInfo = MapEventProcessor.getMapLayerOrder(this.getMapId()); + const curOrderedLayerInfo = MapEventProcessor.getMapOrderedLayerInfo(this.getMapId()); const layerVisibility = MapEventProcessor.getMapVisibilityFromOrderedLayerInfo(this.getMapId(), layerPath); // Determine the outcome of the new visibility based on parameters const newVisibility = newValue !== undefined ? newValue : !layerVisibility; - const layerInfos = curOrderedLayerInfo.filter((info: { layerPath: string }) => info.layerPath.startsWith(layerPath)); + const layerInfos = curOrderedLayerInfo.filter((info: TypeOrderedLayerInfo) => info.layerPath.startsWith(layerPath)); const parentLayerPathArray = layerPath.split('/'); parentLayerPathArray.pop(); const parentLayerPath = parentLayerPathArray.join('/'); - const parentLayerInfo = curOrderedLayerInfo.find((info: { layerPath: string }) => info.layerPath === parentLayerPath); + const parentLayerInfo = curOrderedLayerInfo.find((info: TypeOrderedLayerInfo) => info.layerPath === parentLayerPath); - layerInfos.forEach((layerInfo: { visible: boolean; layerPath: string }) => { + layerInfos.forEach((layerInfo: TypeOrderedLayerInfo) => { if (layerInfo) { // If the new visibility is different than before if (newVisibility !== layerVisibility) { @@ -1431,9 +1431,9 @@ export class LayerApi { } } const children = curOrderedLayerInfo.filter( - (info: { layerPath: string }) => info.layerPath.startsWith(parentLayerPath) && info.layerPath !== parentLayerPath + (info: TypeOrderedLayerInfo) => info.layerPath.startsWith(parentLayerPath) && info.layerPath !== parentLayerPath ); - if (!children.some((child: { visible: boolean }) => child.visible === true)) { + if (!children.some((child: TypeOrderedLayerInfo) => child.visible === true)) { this.setOrToggleLayerVisibility(parentLayerPath, false); // Emit event diff --git a/packages/geoview-core/src/geo/map/map-viewer.ts b/packages/geoview-core/src/geo/map/map-viewer.ts index 9f6bfc0d97b..f5cef27a7fc 100644 --- a/packages/geoview-core/src/geo/map/map-viewer.ts +++ b/packages/geoview-core/src/geo/map/map-viewer.ts @@ -826,7 +826,7 @@ export class MapViewer { * @returns {TypeOrderedLayerInfo[]} The ordered layer info */ getMapLayerOrderInfo(): TypeOrderedLayerInfo[] { - return MapEventProcessor.getMapLayerOrder(this.mapId); + return MapEventProcessor.getMapOrderedLayerInfo(this.mapId); } /** diff --git a/packages/geoview-core/src/ui/button/button.tsx b/packages/geoview-core/src/ui/button/button.tsx index 88830b7d01c..6b5399095fe 100644 --- a/packages/geoview-core/src/ui/button/button.tsx +++ b/packages/geoview-core/src/ui/button/button.tsx @@ -40,8 +40,8 @@ export function Button(props: ButtonProps): JSX.Element { const theme = useTheme(); const mobileView = useMediaQuery(theme.breakpoints.down('md')); - return ( - + function getMaterialButton(): JSX.Element { + return ( {!(makeResponsive && mobileView) ? children : null} + ); + } + + if (disabled) { + return getMaterialButton(); + } + return ( + + {getMaterialButton()} ); } diff --git a/packages/geoview-core/src/ui/tabs/tabs.tsx b/packages/geoview-core/src/ui/tabs/tabs.tsx index 4f6d8ef2caf..e22faf3e1da 100644 --- a/packages/geoview-core/src/ui/tabs/tabs.tsx +++ b/packages/geoview-core/src/ui/tabs/tabs.tsx @@ -10,6 +10,7 @@ import { Select, TypeMenuItemProps } from '@/ui/select/select'; import { getSxClasses } from './tabs-style'; import { TabPanel } from './tab-panel'; import { useMapSize } from '@/core/stores/store-interface-and-intial-values/map-state'; +import { useUIHiddenTabs } from '@/core/stores/store-interface-and-intial-values/ui-state'; /** * Type used for properties of each tab @@ -84,6 +85,7 @@ export function Tabs(props: TypeTabsProps): JSX.Element { // get store values and actions const mapSize = useMapSize(); + const hiddenTabs = useUIHiddenTabs(); // show/hide dropdown based on map size const initMobileDropdown = mapSize[0] !== 0 ? mapSize[0] < theme.breakpoints.values.sm : false; @@ -197,7 +199,7 @@ export function Tabs(props: TypeTabsProps): JSX.Element { iconPosition="start" id={`tab-${index}`} onClick={() => handleClick(index)} - sx={sxClasses.tab} + sx={hiddenTabs.includes(tab.id) ? { display: 'none' } : sxClasses.tab} {...tabProps} /> ); diff --git a/packages/geoview-time-slider/src/time-slider.tsx b/packages/geoview-time-slider/src/time-slider.tsx index 733d40dabc1..0288ee0b281 100644 --- a/packages/geoview-time-slider/src/time-slider.tsx +++ b/packages/geoview-time-slider/src/time-slider.tsx @@ -1,5 +1,6 @@ import { useTheme } from '@mui/material/styles'; import { FormControl, InputLabel, NativeSelect } from '@mui/material'; +import { Box } from 'geoview-core/src/ui'; import { useTimeSliderLayers, useTimeSliderStoreActions, @@ -426,6 +427,7 @@ export function TimeSlider(props: TimeSliderProps): JSX.Element { {locked ? : } )} + + {!isPlaying ? : } + + {reversed ? : } - - {getLocalizedMessage('timeSlider.slider.timeDelay', displayLanguage)} - handleTimeChange(event), - }} - > - - - - - - - - - + + + + {getLocalizedMessage('timeSlider.slider.timeDelay', displayLanguage)} + handleTimeChange(event), + }} + > + + + + + + + + + + {description && (