Skip to content

Commit

Permalink
Finish fix
Browse files Browse the repository at this point in the history
  • Loading branch information
jolevesq committed Dec 19, 2024
1 parent 0349a68 commit c4c644f
Show file tree
Hide file tree
Showing 7 changed files with 145 additions and 86 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -353,7 +353,15 @@ export class LegendEventProcessor extends AbstractEventProcessor {
createNewLegendEntries(2, layers);

// Update the legend layers with the updated array, triggering the subscribe
this.getLayerState(mapId).setterActions.setLegendLayers(layers);
// Reorder the array so legend tab is in synch
const sortedLayers = layers.sort((a, b) =>
MapEventProcessor.getMapIndexFromOrderedLayerInfo(mapId, a.layerPath) >
MapEventProcessor.getMapIndexFromOrderedLayerInfo(mapId, b.layerPath)
? 1
: -1
);

this.getLayerState(mapId).setterActions.setLegendLayers(sortedLayers);
}
// #endregion

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useTheme } from '@mui/material';
import { memo } from 'react';
import { memo, useMemo } from 'react';
import { Box, Collapse, List } from '@/ui';
import { TypeLegendLayer } from '@/core/components/layers/types';
import { getSxClasses } from './legend-styles';
Expand All @@ -24,6 +24,14 @@ interface WMSLegendImageProps {
sxClasses: Record<string, object>;
}

// Constant style outside of render
const styles = {
wmsImage: {
maxWidth: '90%',
cursor: 'pointer',
},
} as const;

// Extracted WMS Legend Component
const WMSLegendImage = memo(
({ imgSrc, initLightBox, legendExpanded, sxClasses }: WMSLegendImageProps): JSX.Element => (
Expand All @@ -32,7 +40,7 @@ const WMSLegendImage = memo(
component="img"
tabIndex={0}
src={imgSrc}
sx={{ maxWidth: '90%', cursor: 'pointer' }}
sx={styles.wmsImage}
onClick={() => initLightBox(imgSrc, '', 0, 2)}
onKeyDown={(e) => (e.code === 'Space' || e.code === 'Enter' ? initLightBox(imgSrc, '', 0, 2) : null)}
/>
Expand All @@ -51,7 +59,7 @@ export const CollapsibleContent = memo(function CollapsibleContent({

// Hooks
const theme = useTheme();
const sxClasses = getSxClasses(theme);
const sxClasses = useMemo(() => getSxClasses(theme), [theme]);

// Props extraction
const { children, items } = layer;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ type ControlActions = {
handleZoomTo: (e: React.MouseEvent) => void;
};

// Constant style outside of render
const styles = {
btnMargin: { marginTop: '-0.3125rem' },
} as const;

// Custom hook for control actions
const useControlActions = (layerPath: string): ControlActions => {
const { setOrToggleLayerVisibility } = useMapStoreActions();
Expand Down Expand Up @@ -73,7 +78,7 @@ const useSubtitle = (children: TypeLegendLayer[], items: TypeLegendItem[]): stri
}, [children.length, items, t]);
};

// SecondaryControls component
// SecondaryControls component (no memo to force re render from layers panel modifications)
export function SecondaryControls({ layer, visibility }: SecondaryControlsProps): JSX.Element {
logger.logTraceRender('components/legend/legend-layer-ctrl');

Expand Down Expand Up @@ -111,12 +116,7 @@ export function SecondaryControls({ layer, visibility }: SecondaryControlsProps)
>
{visibility ? <VisibilityOutlinedIcon /> : <VisibilityOffOutlinedIcon />}
</IconButton>
<IconButton
tooltip="legend.highlightLayer"
sx={{ marginTop: '-0.3125rem' }}
className="buttonOutline"
onClick={controls.handleHighlightLayer}
>
<IconButton tooltip="legend.highlightLayer" sx={styles.btnMargin} className="buttonOutline" onClick={controls.handleHighlightLayer}>
{highlightedLayer === layer.layerPath ? <HighlightIcon /> : <HighlightOutlinedIcon />}
</IconButton>
<IconButton tooltip="legend.zoomTo" className="buttonOutline" onClick={controls.handleZoomTo}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ const LegendListItem = memo(
);
LegendListItem.displayName = 'LegendListItem';

// Item list component (no memo to force re render from layers panel modifications)
export const ItemsList = memo(function ItemsList({ items }: ItemsListProps): JSX.Element | null {
logger.logTraceRender('components/legend/legend-layer-items');

Expand Down
18 changes: 13 additions & 5 deletions packages/geoview-core/src/core/components/legend/legend-layer.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { memo, useCallback } from 'react';
import { memo, useCallback, useMemo } from 'react';
import { useTheme } from '@mui/material';
import { Box, ListItem, Tooltip, ListItemText, IconButton, KeyboardArrowDownIcon, KeyboardArrowUpIcon } from '@/ui';
import { TypeLegendLayer } from '@/core/components/layers/types';
Expand All @@ -8,6 +8,7 @@ import { LayerIcon } from '../common/layer-icon';
import { SecondaryControls } from './legend-layer-ctrl';
import { CollapsibleContent } from './legend-layer-container';
import { getSxClasses } from './legend-styles';
import { logger } from '@/core/utils/logger';

interface LegendLayerProps {
layer: TypeLegendLayer;
Expand All @@ -20,14 +21,21 @@ interface LegendLayerHeaderProps {
onExpandClick: (e: React.MouseEvent) => void;
}

// Constant style outside of render
const styles = {
listItemText: {
'&:hover': { cursor: 'pointer' },
},
} as const;

// Extracted Header Component
const LegendLayerHeader = memo(
({ layer, isCollapsed, isVisible, onExpandClick }: LegendLayerHeaderProps): JSX.Element => (
<ListItem key={layer.layerName} divider onClick={onExpandClick}>
<LayerIcon layer={layer} />
<Tooltip title={layer.layerName} placement="top">
<ListItemText
sx={{ '&:hover': { cursor: 'pointer' } }}
sx={styles.listItemText}
primary={layer.layerName}
className="layerTitle"
disableTypography
Expand All @@ -46,9 +54,11 @@ LegendLayerHeader.displayName = 'LegendLayerHeader';

// Main LegendLayer component
export function LegendLayer({ layer }: LegendLayerProps): JSX.Element {
logger.logTraceRender('components/legend/legend-layer');

// Hooks
const theme = useTheme();
const sxClasses = getSxClasses(theme);
const sxClasses = useMemo(() => getSxClasses(theme), [theme]);

// Stores
const { initLightBox, LightBoxComponent } = useLightBox();
Expand Down Expand Up @@ -90,5 +100,3 @@ export function LegendLayer({ layer }: LegendLayerProps): JSX.Element {
</>
);
}

export default LegendLayer;
168 changes: 99 additions & 69 deletions packages/geoview-core/src/core/components/legend/legend.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useTheme } from '@mui/material';
import { useEffect, useMemo, useState } from 'react';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Box, Typography } from '@/ui';
import { useGeoViewMapId } from '@/core/stores/';
Expand All @@ -18,44 +18,78 @@ interface LegendType {
containerType?: 'appBar' | 'footerBar';
}

// Constant style outside of render (styles)
const styles = {
noLayersContainer: {
padding: '2rem',
margin: '2rem',
width: '100%',
textAlign: 'center',
},
layerBox: {
paddingRight: '0.65rem',
},
flexContainer: {
display: 'flex',
flexDirection: 'row',
flexWrap: 'wrap',
},
} as const;

// Constant style outside of render (responsive widths)
const responsiveWidths = {
full: { xs: '100%' },
responsive: {
xs: '100%',
sm: '50%',
md: '33.33%',
lg: '25%',
xl: '25%',
},
} as const;

export function Legend({ fullWidth, containerType = 'footerBar' }: LegendType): JSX.Element {
logger.logTraceRender('components/legend/legend');

const mapId = useGeoViewMapId();
// Hooks
const { t } = useTranslation<string>();

const theme = useTheme();
const sxClasses = getSxClasses(theme);

// internal state
// State
const [legendLayers, setLegendLayers] = useState<TypeLegendLayer[]>([]);
const [formattedLegendLayerList, setFormattedLegendLayersList] = useState<TypeLegendLayer[][]>([]);

// store state
// Store
const mapId = useGeoViewMapId();
const orderedLayerInfo = useMapOrderedLayerInfo();
const layersList = useLayerLegendLayers();

// Custom hook for calculating the height of footer panel
const { leftPanelRef } = useFooterPanelHeight({ footerPanelTab: 'legend' });

// Memoize breakpoint values
const breakpoints = useMemo(
() => ({
sm: theme.breakpoints.values.sm,
md: theme.breakpoints.values.md,
lg: theme.breakpoints.values.lg,
}),
[theme.breakpoints.values.sm, theme.breakpoints.values.md, theme.breakpoints.values.lg]
);

/**
* Get the size of list based on window size.
*/
const getLegendLayerListSize = useMemo(() => {
return () => {
let size = 4;
// when legend is loaded in appbar size will always be 1.
if (containerType === CONTAINER_TYPE.APP_BAR) return 1;
if (window.innerWidth < theme.breakpoints.values.sm) {
size = 1;
} else if (window.innerWidth < theme.breakpoints.values.md) {
size = 2;
} else if (window.innerWidth < theme.breakpoints.values.lg) {
size = 3;
}
return size;
};
}, [theme.breakpoints.values.lg, theme.breakpoints.values.md, theme.breakpoints.values.sm, containerType]);
const getLegendLayerListSize = useCallback(() => {
if (containerType === CONTAINER_TYPE.APP_BAR) return 1;

const { innerWidth } = window;
if (innerWidth < breakpoints.sm) return 1;
if (innerWidth < breakpoints.md) return 2;
if (innerWidth < breakpoints.lg) return 3;
return 4;
}, [breakpoints, containerType]);

/**
* Transform the list of the legends into subsets of lists.
Expand All @@ -64,73 +98,69 @@ export function Legend({ fullWidth, containerType = 'footerBar' }: LegendType):
* @param {TypeLegendLayer} layers array of layers.
* @returns List of array of layers
*/
const updateLegendLayerListByWindowSize = (layers: TypeLegendLayer[]): void => {
const arrSize = getLegendLayerListSize();

// create list of arrays based on size of the window.
const list = Array.from({ length: arrSize }, () => []) as Array<TypeLegendLayer[]>;
layers.forEach((layer, index) => {
const idx = index % arrSize;
list[idx].push(layer);
});
setFormattedLegendLayersList(list);
};
const updateLegendLayerListByWindowSize = useCallback(
(layers: TypeLegendLayer[]): void => {
const arrSize = getLegendLayerListSize();
const list = Array.from({ length: arrSize }, () => []) as Array<TypeLegendLayer[]>;

layers.forEach((layer, index) => {
list[index % arrSize].push(layer);
});

setFormattedLegendLayersList(list);
},
[getLegendLayerListSize]
);

// Handle initial layer setup
useEffect(() => {
// Log
logger.logTraceUseEffect('LEGEND - visibleLayers', orderedLayerInfo.length, orderedLayerInfo);
logger.logTraceUseEffect('LEGEND - layer setup', orderedLayerInfo.length, orderedLayerInfo, layersList);
setLegendLayers(layersList);
updateLegendLayerListByWindowSize(layersList);
}, [orderedLayerInfo, layersList, updateLegendLayerListByWindowSize]);

// eslint-disable-next-line react-hooks/exhaustive-deps
}, [orderedLayerInfo, layersList]);

// Handle window resize
useEffect(() => {
// Log
logger.logTraceUseEffect('LEGEND - legendLayers', legendLayers);
logger.logTraceUseEffect('LEGEND - window resize', legendLayers);

// update subsets of list when window size updated.
const formatLegendLayerList = (): void => {
// Log
logger.logTraceCore('LEGEND - window resize event');

updateLegendLayerListByWindowSize(legendLayers);
};
window.addEventListener('resize', formatLegendLayerList);

return () => window.removeEventListener('resize', formatLegendLayerList);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [legendLayers]);
}, [legendLayers, updateLegendLayerListByWindowSize]);

// Memoize the rendered content based on whether there are legend layers
const content = useMemo(() => {
if (!legendLayers.length) {
return (
<Box sx={styles.noLayersContainer}>
<Typography variant="h3" gutterBottom sx={sxClasses.legendInstructionsTitle}>
{t('legend.noLayersAdded')}
</Typography>
<Typography component="p" sx={sxClasses.legendInstructionsBody}>
{t('legend.noLayersAddedDescription')}
</Typography>
</Box>
);
}

return formattedLegendLayerList.map((layers, idx) => (
<Box key={`${idx.toString()}`} width={fullWidth ? responsiveWidths.full : responsiveWidths.responsive} sx={styles.layerBox}>
{layers.map((layer) => (
<LegendLayer layer={layer} key={layer.layerPath} />
))}
</Box>
));
}, [legendLayers, formattedLegendLayerList, fullWidth, sxClasses, t]);

return (
<Box sx={sxClasses.container} {...(!fullWidth && { ref: leftPanelRef })} id={`${mapId}-${containerType}-legendContainer`}>
<Box display="flex" flexDirection="row" flexWrap="wrap">
{!!legendLayers.length &&
formattedLegendLayerList.map((layers, idx) => {
return (
<Box
key={`${idx.toString()}`}
width={fullWidth ? { xs: '100%' } : { xs: '100%', sm: '50%', md: '33.33%', lg: '25%', xl: '25%' }}
sx={{ paddingRight: '0.65rem' }}
>
{layers.map((layer) => {
return <LegendLayer layer={layer} key={layer.layerPath} />;
})}
</Box>
);
})}

{/* Show legend Instructions when no layer found. */}
{!legendLayers.length && (
<Box sx={{ padding: '2rem', margin: '2rem', width: '100%', textAlign: 'center' }}>
<Typography variant="h3" gutterBottom sx={sxClasses.legendInstructionsTitle}>
{t('legend.noLayersAdded')}
</Typography>
<Typography component="p" sx={sxClasses.legendInstructionsBody}>
{t('legend.noLayersAddedDescription')}
</Typography>
</Box>
)}
</Box>
<Box sx={styles.flexContainer}>{content}</Box>
</Box>
);
}
Loading

0 comments on commit c4c644f

Please sign in to comment.