Skip to content

Commit

Permalink
fix(UI): refactor appbar component #closes2145 (#2147)
Browse files Browse the repository at this point in the history
* fix(appbar): refactor appbar component   #closes2114

* fix(appbar): refactor appbar component   #closes2114

* fix(UI): refactor attribution   #closes2114

* fix(UI): refactor fullscreen dialog   #closes2114

* fix(UI): refactor layer list   #closes2114

* fix(UI): refactor layout compoennt   #closes2114

* fix(UI): refactor responsive grid layout   #closes2114

* fix(UI): refactor responsive grid layout   #closes2114

* fix(UI): refactor app bar   #closes2114

* fix(UI): refactor footer panel hook   #closes2114

* fix(UI): refactor crosshair   #closes2114

* fix(UI): refactor crosshair   #closes2114
  • Loading branch information
kaminderpal authored May 21, 2024
1 parent d55b22f commit eb50540
Show file tree
Hide file tree
Showing 18 changed files with 221 additions and 173 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -178,3 +178,8 @@ export const CV_DEFAULT_LAYER_INITIAL_SETTINGS = {
queryable: false,
},
};

/**
* Definition of the default order of the tabs inside appbar
*/
export const CV_DEFAULT_APPBAR_TABS_ORDER = ['legend', 'details', 'guide'];
Original file line number Diff line number Diff line change
Expand Up @@ -140,8 +140,13 @@ export class FeatureInfoEventProcessor extends AbstractEventProcessor {
// If there was some features on this propagation
if (atLeastOneFeature) {
// If the current tab is not 'details' nor 'geochart', switch to details
if (!['details', 'geochart'].includes(UIEventProcessor.getActiveFooterBarTab(mapId)))
if (!['details', 'geochart'].includes(UIEventProcessor.getActiveFooterBarTab(mapId))) {
UIEventProcessor.setActiveFooterBarTab(mapId, 'details');
}

if (UIEventProcessor.getActiveAppBarTabId(mapId) !== 'AppbarPanelButtonDetails') {
UIEventProcessor.setActiveAppBarTabId(mapId, 'AppbarPanelButtonDetails');
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ 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 @@ -56,4 +60,8 @@ export class UIEventProcessor extends AbstractEventProcessor {
static setActiveFooterBarTab(mapId: string, id: string): void {
this.getUIState(mapId).setterActions.setActiveFooterBarTab(id);
}

static setActiveAppBarTabId(mapId: string, id: string): void {
this.getUIState(mapId).setterActions.setActiveAppBarTabId(id);
}
}
59 changes: 35 additions & 24 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,8 +9,8 @@ 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,
useUIActiveFooterBarTabId,
useUIAppbarComponents,
useUIAppbarGeolocatorActive,
useUIStoreActions,
Expand All @@ -28,6 +28,7 @@ import { getSxClasses } from './app-bar-style';
import { enforceArrayOrder, helpCloseAll, helpClosePanelById, helpOpenPanelById } from './app-bar-helper';
import { TypeJsonObject, TypeJsonValue, toJsonObject } from '@/core/types/global-types';
import { AbstractPlugin } from '@/api/plugin/abstract-plugin';
import { CV_DEFAULT_APPBAR_TABS_ORDER } from '@/api/config/types/config-constants';

interface GroupPanelType {
icon: ReactNode;
Expand Down Expand Up @@ -56,19 +57,17 @@ export function AppBar(props: AppBarProps): JSX.Element {

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

// get store values and action
const activeModalId = useUIActiveFocusItem().activeElementId;
const interaction = useMapInteraction();
const appBarComponents = useUIAppbarComponents();
const { hideClickMarker } = useMapStoreActions();
const activeAppBarTabId = useUIActiveAppBarTabId();
const geoviewElement = useAppGeoviewHTMLElement().querySelector('[id^="mapTargetElement-"]') as HTMLElement;
// TODO: remove active footerTab Id and create new one for AppBar id.
const activeFooterTabId = useUIActiveFooterBarTabId();

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

// get store config for app bar to add (similar logic as in footer-bar)
Expand Down Expand Up @@ -106,9 +105,9 @@ export function AppBar(props: AppBarProps): JSX.Element {

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

const closeAll = useCallback(() => {
Expand All @@ -126,9 +125,9 @@ export function AppBar(props: AppBarProps): JSX.Element {

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

const handleButtonClicked = useCallback(
Expand Down Expand Up @@ -157,11 +156,11 @@ export function AppBar(props: AppBarProps): JSX.Element {

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

// Close it
closePanelById(selectedAppBarButtonId, undefined);
}, [selectedAppBarButtonId, closePanelById]);
closePanelById(activeAppBarTabId, undefined);
}, [activeAppBarTabId, closePanelById]);

const handleAddButtonPanel = useCallback(
(sender: AppBarApi, event: AppBarCreatedEvent) => {
Expand Down Expand Up @@ -218,9 +217,8 @@ export function AppBar(props: AppBarProps): JSX.Element {
useEffect(() => {
// Log
logger.logTraceUseEffect('APP-BAR - open detail panel when clicked on map', mapId);
// TODO: remove active footerTab Id and create new one for AppBar id.
// open AppBar detail drawer when click on map.
if (activeFooterTabId === 'details' && buttonPanelGroups?.details?.AppbarPanelButtonDetails?.panel) {
if (activeAppBarTabId === 'AppbarPanelButtonDetails' && buttonPanelGroups?.details?.AppbarPanelButtonDetails?.panel) {
// close geolocator when user click on map layer.
if (isGeolocatorActive) {
setGeolocatorActive(false);
Expand All @@ -229,7 +227,7 @@ export function AppBar(props: AppBarProps): JSX.Element {
openPanelById(buttonPanelGroups?.details?.AppbarPanelButtonDetails?.button?.id || '', undefined);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [activeFooterTabId]);
}, [activeAppBarTabId]);

/**
* Create default tabs from configuration parameters (similar logic as in footer-bar).
Expand Down Expand Up @@ -268,7 +266,7 @@ export function AppBar(props: AppBarProps): JSX.Element {

// render footer bar tabs
(appBarConfig?.tabs.core ?? [])
.filter((tab) => tab === 'guide' || tab === 'details' || tab === 'legend')
.filter((tab) => CV_DEFAULT_APPBAR_TABS_ORDER.includes(tab))
.map((tab): [TypeIconButtonProps, TypePanelProps, string] => {
const button: TypeIconButtonProps = {
id: `AppbarPanelButton${capitalize(tab)}`,
Expand All @@ -290,16 +288,29 @@ export function AppBar(props: AppBarProps): JSX.Element {
return [button, panel, tab];
})
.forEach((footerGroup) => appBarApi.createAppbarPanel(footerGroup[0], footerGroup[1], footerGroup[2]));
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [appBarConfig?.tabs.core, appBarApi]); // Not exhaustive, because it'd be dangerous to trigger on `panels` or on `t`, because of how the AppBar panels are just recreated all the time (should refactor this, maybe..)
}, [appBarConfig?.tabs.core, appBarApi, t, memoPanels]);

// #endregion

let buttonPanelGroupNames = Object.keys(buttonPanelGroups);
buttonPanelGroupNames = enforceArrayOrder(buttonPanelGroupNames, ['legend', 'details', 'guide']);
const topGroupNames = buttonPanelGroupNames.filter((groupName) => groupName !== 'guide');
const bottomGroupNames = buttonPanelGroupNames.filter((groupName) => groupName === 'guide');
/**
* Re-order the appbar buttons.
*/
const { topGroupNames, bottomGroupNames } = useMemo(() => {
// Log
logger.logTraceUseMemo('APP-BAR - panels');

let buttonPanelGroupNames = Object.keys(buttonPanelGroups);
buttonPanelGroupNames = enforceArrayOrder(buttonPanelGroupNames, CV_DEFAULT_APPBAR_TABS_ORDER);
const topGroup = buttonPanelGroupNames.filter((groupName) => groupName !== 'guide');
const bottomGroup = buttonPanelGroupNames.filter((groupName) => groupName === 'guide');
return { topGroupNames: topGroup, bottomGroupNames: bottomGroup };
}, [buttonPanelGroups]);

/**
* Render Tab groups in appbar.
* @param {string[]} groupNames group that will be rendered in appbar.
* @returns JSX.Element
*/
const renderButtonGroup = (groupNames: string[]): ReactNode => {
return (
<>
Expand All @@ -320,9 +331,9 @@ export function AppBar(props: AppBarProps): JSX.Element {
aria-label={buttonPanel.button.tooltip}
tooltip={buttonPanel.button.tooltip}
tooltipPlacement="right"
className={`style3 ${selectedAppBarButtonId === buttonPanel.button.id ? 'active' : ''}`}
className={`style3 ${activeAppBarTabId === buttonPanel.button.id ? 'active' : ''}`}
size="small"
onClick={(): void => handleButtonClicked(buttonPanel.button.id!, groupName)}
onClick={() => handleButtonClicked(buttonPanel.button.id!, groupName)}
>
{buttonPanel.button.children}
</IconButton>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useState } from 'react';
import { useCallback, useState } from 'react';

import { useTheme } from '@mui/material/styles';

Expand Down Expand Up @@ -30,17 +30,15 @@ export function Attribution(): JSX.Element {

// getStore value
const mapAttribution = useMapAttribution();
const expanded = useUIMapInfoExpanded();

const handleOpenPopover = (event: React.MouseEvent<HTMLButtonElement>): void => {
const handleOpenPopover = useCallback((event: React.MouseEvent<HTMLButtonElement>): void => {
setAnchorEl(event.currentTarget);
};
}, []);

const handleClosePopover = (): void => {
const handleClosePopover = useCallback(() => {
setAnchorEl(null);
};

// get store value
const expanded = useUIMapInfoExpanded();
}, []);

return (
<>
Expand All @@ -52,9 +50,9 @@ export function Attribution(): JSX.Element {
tooltip="mapctrl.attribution.tooltip"
sx={{
color: theme.palette.geoViewColor.bgColor.light[800],
marginTop: expanded ? '12px' : '4px',
marginTop: expanded ? '0.75rem' : '0.25rem',
[theme.breakpoints.up('md')]: {
marginTop: expanded ? '23px' : 'none',
marginTop: expanded ? '1.4375rem' : 'none',
},
width: '30px',
height: '30px',
Expand All @@ -77,7 +75,7 @@ export function Attribution(): JSX.Element {
}}
onClose={handleClosePopover}
>
<Box sx={{ padding: '15px', width: '450px' }}>
<Box sx={{ padding: '1rem', width: '28.125rem' }}>
{mapAttribution.map((attribution) => {
return <Typography key={generateId()}>{attribution}</Typography>;
})}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,7 @@ interface FullScreenDialogProps extends DialogProps {
children: ReactNode;
}

function FullScreenDialog(props: FullScreenDialogProps): JSX.Element {
const { open, onClose, children } = props;

function FullScreenDialog({ open, onClose, children }: FullScreenDialogProps): JSX.Element {
return (
<Dialog fullScreen maxWidth="xl" open={open} onClose={onClose} disablePortal>
<DialogContent sx={{ display: 'flex', flexDirection: 'column', alignItems: 'end' }}>
Expand Down
82 changes: 41 additions & 41 deletions packages/geoview-core/src/core/components/common/layer-list.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ReactNode, memo } from 'react';
import { ReactNode, memo, useCallback } from 'react';
import { useTheme } from '@mui/material/styles';
import { useTranslation } from 'react-i18next';
import { animated, useSpring } from '@react-spring/web';
Expand Down Expand Up @@ -48,55 +48,49 @@ const LayerListItem = memo(function LayerListItem({ isSelected, layer, onListIte
layer.layerStatus === 'loading' ||
layer.layerStatus === 'processing';

/**
* Render Layer Icon based on layerPath
* @returns
*/
const renderLayerIcon = (): JSX.Element | null => {
switch (layer.layerStatus) {
case 'error':
return null;

default:
// If there is content, this is a guide section with no icon
if (layer.content) return null;
// If there's a layer path
if (layer.layerPath) {
return (
<ListItemIcon aria-hidden="true">
<LayerIcon layer={layer} />
</ListItemIcon>
);
}
return null;
if (layer.layerPath) {
return (
<ListItemIcon aria-hidden="true">
<LayerIcon layer={layer} />
</ListItemIcon>
);
}
return null;
};

const renderLayerStatus = (): JSX.Element | string | null => {
switch (layer.layerStatus) {
case 'error':
return t('legend.layerError');

default:
switch (layer.queryStatus) {
case 'init':
case 'processing':
return `${t('layers.querying')}...`;
case 'error':
return t('legend.layerError');
default:
return (
<>
{layer.layerFeatures} {layer?.mapFilteredIcon ?? ''}
</>
);
}
/**
* Get layer status based on query status and layer status
*/
const getLayerStatus = useCallback((): JSX.Element | string => {
if (layer.layerStatus === 'error' || layer?.queryStatus === 'error') {
return `${t('legend.layerError')}`;
}
};
if (['init', 'processing'].includes(layer.queryStatus)) {
return `${t('layers.querying')}...`;
}
return (
<>
{layer.layerFeatures} {layer?.mapFilteredIcon ?? ''}
</>
);
}, [layer, t]);

/**
* Render Layer body.
* @returns JSX.Element
*/
const renderLayerBody = (): JSX.Element => {
return (
<Box sx={sxClasses.listPrimaryText}>
<Typography className="layerTitle">{layer.layerName}</Typography>
<Box display="flex" alignContent="center">
<Typography component="p" variant="subtitle1" noWrap display="flex">
{renderLayerStatus()}
{getLayerStatus()}
</Typography>
</Box>
</Box>
Expand All @@ -119,9 +113,15 @@ const LayerListItem = memo(function LayerListItem({ isSelected, layer, onListIte
to: { opacity: 1 },
});

const handleLayerKeyDown = (e: React.KeyboardEvent, selectedLayer: LayerListEntry): void => {
if (e.key === 'Enter') onListItemClick(selectedLayer);
};
/**
* Handle layer click when mouse enter is pressed.
*/
const handleLayerKeyDown = useCallback(
(e: React.KeyboardEvent, selectedLayer: LayerListEntry): void => {
if (e.key === 'Enter') onListItemClick(selectedLayer);
},
[onListItemClick]
);

const AnimatedPaper = animated(Paper);

Expand Down
Loading

0 comments on commit eb50540

Please sign in to comment.