Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(Appbar): refactor appbar for export modal #closes2161 #2174

Merged
Original file line number Diff line number Diff line change
Expand Up @@ -146,8 +146,9 @@ export class FeatureInfoEventProcessor extends AbstractEventProcessor {
UIEventProcessor.setActiveFooterBarTab(mapId, 'details');
}

if (UIEventProcessor.getActiveAppBarTabId(mapId) !== 'AppbarPanelButtonDetails') {
UIEventProcessor.setActiveAppBarTabId(mapId, 'AppbarPanelButtonDetails');
// Open details appbar tab when user clicked on map layer.
if (UIEventProcessor.getAppBarComponents(mapId).includes('details')) {
UIEventProcessor.setActiveAppBarTab(mapId, 'AppbarPanelButtonDetails', 'details', true);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { TypeMapCorePackages, TypeValidAppBarCoreProps } from '@config/types/map-schema-types';
import { AbstractEventProcessor } from '@/api/event-processors/abstract-event-processor';
import { IUIState } from '@/core/stores/store-interface-and-intial-values/ui-state';
import { IUIState, ActiveAppBarTabType } from '@/core/stores/store-interface-and-intial-values/ui-state';

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

Expand All @@ -27,10 +27,6 @@ export class UIEventProcessor extends AbstractEventProcessor {
return this.getUIState(mapId).activeFooterBarTabId;
}

static getActiveAppBarTabId(mapId: string): string {
return this.getUIState(mapId).activeAppBarTabId;
}

static getAppBarComponents(mapId: string): TypeValidAppBarCoreProps {
return this.getUIState(mapId).appBarComponents;
}
Expand All @@ -49,7 +45,11 @@ export class UIEventProcessor extends AbstractEventProcessor {
this.getUIState(mapId).setterActions.setActiveFooterBarTab(id);
}

static setActiveAppBarTabId(mapId: string, id: string): void {
this.getUIState(mapId).setterActions.setActiveAppBarTabId(id);
static setActiveAppBarTab(mapId: string, tabId: string, tabGroup: string, isOpen: boolean): void {
this.getUIState(mapId).setterActions.setActiveAppBarTab(tabId, tabGroup, isOpen);
}

static getActiveAppBarTab(mapId: string): ActiveAppBarTabType {
return this.getUIState(mapId).activeAppBarTab;
}
}
77 changes: 30 additions & 47 deletions packages/geoview-core/src/core/components/app-bar/app-bar-helper.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import { Dispatch, SetStateAction } from 'react';
import { TypeButtonPanel } from '@/ui/panel/panel-types';
import { ButtonPanelGroupType, ButtonPanelType } from './app-bar';

export const helpFindGroupName = (
buttonPanelGroups: Record<string, Record<string, TypeButtonPanel>>,
buttonId: string
): string | undefined => {
export const helpFindGroupName = (buttonPanelGroups: ButtonPanelGroupType, buttonId: string): string | undefined => {
let groupName: string | undefined;
Object.entries(buttonPanelGroups).forEach(([buttonPanelGroupName, buttonPanelGroup]) => {
if (!groupName) {
Expand All @@ -18,61 +16,42 @@ export const helpFindGroupName = (
};

export const helpOpenClosePanelByIdState = (
buttonPanelGroups: Record<string, Record<string, TypeButtonPanel>>,
buttonPanelGroups: ButtonPanelGroupType,
buttonId: string,
groupName: string | undefined,
setterCallback: Dispatch<SetStateAction<Record<string, Record<string, TypeButtonPanel>>>>,
setterCallback: Dispatch<SetStateAction<ButtonPanelGroupType>>,
status: boolean
): void => {
// Read the group name
const theGroupName = groupName || helpFindGroupName(buttonPanelGroups, buttonId);
if (!theGroupName) return;

// Open or Close it
setterCallback((prevState) => {
// Check if doing it
const doIt = !!(
prevState[theGroupName] &&
prevState[theGroupName][buttonId] &&
prevState[theGroupName][buttonId].panel &&
prevState[theGroupName][buttonId].panel?.status !== status
);

// If is open/closed right now
if (doIt) {
return {
...prevState,
[theGroupName]: {
...prevState[theGroupName],
[buttonId]: {
...prevState[theGroupName][buttonId],
panel: {
...prevState[theGroupName][buttonId].panel!,
status,
},
},
},
};
}
const panelGroups = {} as ButtonPanelGroupType;
Object.entries(prevState).forEach(([buttonPanelGroupName, buttonPanelGroup]) => {
panelGroups[buttonPanelGroupName] = Object.entries(buttonPanelGroup).reduce((acc, [buttonGroupName, buttonGroup]) => {
acc[buttonGroupName] = {
...buttonGroup,
...(buttonGroup.panel && { panel: { ...buttonGroup.panel, status: buttonGroupName === buttonId ? status : false } }),
};

return acc;
}, {} as ButtonPanelType);
});

// Leave as-is
return prevState;
return panelGroups;
});
};

export const helpOpenPanelById = (
buttonPanelGroups: Record<string, Record<string, TypeButtonPanel>>,
buttonPanelGroups: ButtonPanelGroupType,
buttonId: string,
groupName: string | undefined,
setterCallback: Dispatch<SetStateAction<Record<string, Record<string, TypeButtonPanel>>>>,
closeAllCallback: () => void
setterCallback: Dispatch<SetStateAction<ButtonPanelGroupType>>
): void => {
// Read the group name
const theGroupName = groupName || helpFindGroupName(buttonPanelGroups, buttonId);

// Close any already opened panels
closeAllCallback();

// Open the panel
helpOpenClosePanelByIdState(buttonPanelGroups, buttonId, theGroupName, setterCallback, true);
};
Expand Down Expand Up @@ -102,17 +81,21 @@ export const helpClosePanelById = (
};

export const helpCloseAll = (
buttonPanelGroups: Record<string, Record<string, TypeButtonPanel>>,
closeCallback: (buttonId: string, groupName: string | undefined) => void
buttonPanelGroups: ButtonPanelGroupType,
setterCallback: Dispatch<SetStateAction<ButtonPanelGroupType>>
): void => {
// For each group
const panelGroups = {} as ButtonPanelGroupType;
Object.entries(buttonPanelGroups).forEach(([buttonPanelGroupName, buttonPanelGroup]) => {
// For each button
Object.keys(buttonPanelGroup).forEach((buttonId) => {
// Callback to close it
closeCallback(buttonId, buttonPanelGroupName);
});
panelGroups[buttonPanelGroupName] = Object.entries(buttonPanelGroup).reduce((acc, [buttonGroupName, buttonGroup]) => {
acc[buttonGroupName] = {
...buttonGroup,
...(buttonGroup.panel && { panel: { ...buttonGroup.panel, status: false } }),
};
return acc;
}, {} as ButtonPanelType);
});

setterCallback(panelGroups);
};

export const enforceArrayOrder = (sourceArray: string[], enforce: string[]): string[] => {
Expand Down
82 changes: 40 additions & 42 deletions packages/geoview-core/src/core/components/app-bar/app-bar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ import { Plugin } from '@/api/plugin/plugin';
import { TypeButtonPanel, TypePanelProps } from '@/ui/panel/panel-types';
import ExportButton from '@/core/components/export/export-modal-button';
import {
useUIActiveAppBarTabId,
useUIActiveFocusItem,
useUIAppbarComponents,
useUIAppbarGeolocatorActive,
useActiveAppBarTab,
useUIStoreActions,
} from '@/core/stores/store-interface-and-intial-values/ui-state';
import { useMapInteraction, useMapStoreActions } from '@/core/stores/store-interface-and-intial-values/map-state';
Expand All @@ -39,6 +39,13 @@ type AppBarProps = {
api: AppBarApi;
};

export interface ButtonPanelType {
[panelType: string]: TypeButtonPanel;
}
export interface ButtonPanelGroupType {
[panelId: string]: ButtonPanelType;
}

/**
* Create an app-bar with buttons that can open a panel
*/
Expand All @@ -56,19 +63,19 @@ export function AppBar(props: AppBarProps): JSX.Element {
const sxClasses = getSxClasses(theme);

// internal component state
const [buttonPanelGroups, setButtonPanelGroups] = useState<Record<string, Record<string, TypeButtonPanel>>>({});
const [buttonPanelGroups, setButtonPanelGroups] = useState<ButtonPanelGroupType>({});
const appBar = useRef<HTMLDivElement>(null);

// get store values and action
const activeModalId = useUIActiveFocusItem().activeElementId;
const interaction = useMapInteraction();
const appBarComponents = useUIAppbarComponents();
const isGeolocatorActive = useUIAppbarGeolocatorActive();
const { tabId, tabGroup, isOpen } = useActiveAppBarTab();
const { hideClickMarker } = useMapStoreActions();
const activeAppBarTabId = useUIActiveAppBarTabId();
const geoviewElement = useAppGeoviewHTMLElement().querySelector('[id^="mapTargetElement-"]') as HTMLElement;

const { setGeolocatorActive, setActiveAppBarTabId } = useUIStoreActions();
const isGeolocatorActive = useUIAppbarGeolocatorActive();
const { setGeolocatorActive, setActiveAppBarTab } = useUIStoreActions();

// get store config for app bar to add (similar logic as in footer-bar)
const appBarConfig = useGeoViewConfig()?.appBar;
Expand All @@ -81,7 +88,7 @@ export function AppBar(props: AppBarProps): JSX.Element {

// TODO: Refactor - We should find a way to make this 'dictionary of supported components' dynamic.
return {
legend: { icon: <HubOutlinedIcon />, content: <Legend fullWidth /> },
legend: { icon: <HubOutlinedIcon />, content: <Legend fullWidth containerType="appBar" /> },
guide: { icon: <SchoolIcon />, content: <GuidePanel fullWidth /> },
details: { icon: <InfoOutlinedIcon />, content: <DetailsPanel fullWidth /> },
} as unknown as Record<string, GroupPanelType>;
Expand All @@ -95,72 +102,59 @@ export function AppBar(props: AppBarProps): JSX.Element {
// Callback when removing and focus is lost
const focusWhenNoElementCallback = (): void => {
const mapCont = geoviewElement;
mapCont.focus();
mapCont?.focus();

// if in focus trap mode, trigger the event
if (mapCont.closest('.geoview-map')?.classList.contains('map-focus-trap')) {
if (mapCont?.closest('.geoview-map')?.classList.contains('map-focus-trap')) {
mapCont.classList.add('keyboard-focus');
}
};

// Redirect to helper
helpClosePanelById(mapId, buttonPanelGroups, buttonId, groupName, setButtonPanelGroups, focusWhenNoElementCallback);
setActiveAppBarTabId('');
},
[buttonPanelGroups, geoviewElement, mapId, setActiveAppBarTabId]
[buttonPanelGroups, geoviewElement, mapId]
);

const closeAll = useCallback(() => {
// Log
logger.logTraceUseCallback('APP-BAR - closeAll');

// Redirect to helper
helpCloseAll(buttonPanelGroups, closePanelById);
}, [buttonPanelGroups, closePanelById]);
// reset the active appbar tab
setActiveAppBarTab('', '', false);
helpCloseAll(buttonPanelGroups, setButtonPanelGroups);
}, [buttonPanelGroups, setActiveAppBarTab]);

const openPanelById = useCallback(
(buttonId: string, groupName: string | undefined) => {
// Log
logger.logTraceUseCallback('APP-BAR - openPanelById', buttonId);

// Redirect to helper
helpOpenPanelById(buttonPanelGroups, buttonId, groupName, setButtonPanelGroups, closeAll);
setActiveAppBarTabId(buttonId);
helpOpenPanelById(buttonPanelGroups, buttonId, groupName, setButtonPanelGroups);
},
[buttonPanelGroups, closeAll, setActiveAppBarTabId]
[buttonPanelGroups]
);

const handleButtonClicked = useCallback(
(buttonId: string, groupName: string) => {
// Log
logger.logTraceUseCallback('APP-BAR - handleButtonClicked', buttonId);

// close geolocator if opened when panel is open.
if (isGeolocatorActive) {
setGeolocatorActive(false);
}

// Get the button panel
const buttonPanel = buttonPanelGroups[groupName][buttonId];

if (!buttonPanel.panel?.status) {
// Redirect
openPanelById(buttonId, groupName);
} else {
// Redirect
closePanelById(buttonId, groupName);
}
setActiveAppBarTab(buttonId, groupName, !buttonPanel.panel?.status);
},
[buttonPanelGroups, closePanelById, isGeolocatorActive, openPanelById, setGeolocatorActive]
[buttonPanelGroups, setActiveAppBarTab]
);

const handleGeneralCloseClicked = useCallback(() => {
// Log
logger.logTraceUseCallback('APP-BAR - handleGeneralCloseClicked', activeAppBarTabId);
logger.logTraceUseCallback('APP-BAR - handleGeneralCloseClicked');

// Close it
closePanelById(activeAppBarTabId, undefined);
}, [activeAppBarTabId, closePanelById]);
setActiveAppBarTab(tabId, tabGroup, false);
}, [setActiveAppBarTab, tabGroup, tabId]);

const handleAddButtonPanel = useCallback(
(sender: AppBarApi, event: AppBarCreatedEvent) => {
Expand Down Expand Up @@ -212,22 +206,26 @@ export function AppBar(props: AppBarProps): JSX.Element {
appBarApi.offAppBarCreated(handleAddButtonPanel);
appBarApi.offAppBarRemoved(handleRemoveButtonPanel);
};
}, [appBarApi, handleAddButtonPanel, handleRemoveButtonPanel]);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [appBarApi]);

useEffect(() => {
// Log
logger.logTraceUseEffect('APP-BAR - open detail panel when clicked on map', mapId);
// open AppBar detail drawer when click on map.
if (activeAppBarTabId === 'AppbarPanelButtonDetails' && buttonPanelGroups?.details?.AppbarPanelButtonDetails?.panel) {
// close geolocator when user click on map layer.
logger.logTraceUseEffect('APP-BAR - PANEL - OPEN/CLOSE ', isOpen);

if (isOpen) {
// close geolocator if opened when panel is open.
if (isGeolocatorActive) {
setGeolocatorActive(false);
}
// Open it
openPanelById(buttonPanelGroups?.details?.AppbarPanelButtonDetails?.button?.id || '', undefined);
openPanelById(tabId, tabGroup);
} else {
closePanelById(tabId, tabGroup);
}
// NOTE: Run this effect when isOpen, tabId, tabGroup changes
// should not re-render when openPanelById, closePanelById changes.
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [activeAppBarTabId]);
}, [isOpen, tabId, tabGroup]);

/**
* Create default tabs from configuration parameters (similar logic as in footer-bar).
Expand Down Expand Up @@ -331,7 +329,7 @@ export function AppBar(props: AppBarProps): JSX.Element {
aria-label={buttonPanel.button.tooltip}
tooltip={buttonPanel.button.tooltip}
tooltipPlacement="right"
className={`style3 ${activeAppBarTabId === buttonPanel.button.id ? 'active' : ''}`}
className={`style3 ${tabId === buttonPanel.button.id && isOpen ? 'active' : ''}`}
size="small"
onClick={() => handleButtonClicked(buttonPanel.button.id!, groupName)}
>
Expand Down
Loading
Loading