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(UI): refactor appbar component #closes2145 #2147

Merged
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 footer tab 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
Loading