Skip to content

Commit

Permalink
refactor(store): Refactor components to use new store, only miss bigg…
Browse files Browse the repository at this point in the history
…est one

Closes #1461
  • Loading branch information
Johann Levesque committed Nov 6, 2023
1 parent b0f0a3b commit 79ec7bb
Show file tree
Hide file tree
Showing 19 changed files with 192 additions and 245 deletions.
108 changes: 28 additions & 80 deletions packages/geoview-core/src/core/components/crosshair/crosshair.tsx
Original file line number Diff line number Diff line change
@@ -1,129 +1,77 @@
import { useEffect, useRef, useContext } from 'react';
import { useCallback, useEffect, useRef } from 'react';

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

import { useTranslation } from 'react-i18next';

import { toLonLat } from 'ol/proj';
import { KeyboardPan } from 'ol/interaction';

import { getGeoViewStore } from '@/core/stores/stores-managers';

import { MapContext } from '@/core/app-start';

import { Box, Fade, Typography } from '@/ui';
import { TypeMapMouseInfo } from '@/api/events/payloads';

import { getSxClasses } from './crosshair-style';
import { CrosshairIcon } from './crosshair-icon';
import { useAppCrosshairsActive } from '@/core/stores/store-interface-and-intial-values/app-state';
import { useMapCenterCoordinates, useMapElement, useMapProjection } from '@/core/stores/store-interface-and-intial-values/map-state';
import { useMapElement, useMapStoreActions } from '@/core/stores/store-interface-and-intial-values/map-state';

/**
* Create a Crosshair when map is focus with the keyboard so user can click on the map
* @returns {JSX.Element} the crosshair component
*/
export function Crosshair(): JSX.Element {
const mapConfig = useContext(MapContext);
const { mapId } = mapConfig;

const { t } = useTranslation<string>();

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

// get store values
// tracks if the last action was done through a keyboard (map navigation) or mouse (mouse movement)
const store = getGeoViewStore(mapId);
const isCrosshairsActive = useAppCrosshairsActive();
const projection = useMapProjection();
const mapCoord = useMapCenterCoordinates();
const mapElement = useMapElement();

// use reference as the mapElement from the store is undefined
// TODO: Find what is going on with mapElement for focus-trap and crosshair and crosshair + map coord for this component
// ? maybe because simulate click is in an event listener, it is best to use useRef
const isCrosshairsActiveRef = useRef(isCrosshairsActive);
isCrosshairsActiveRef.current = isCrosshairsActive;
const mapCoordRef = useRef(mapCoord);
mapCoordRef.current = mapCoord;
const { setClickCoordinates, setMapKeyboardPanInteractions } = useMapStoreActions();

// do not use useState for item used inside function only without rendering... use useRef
const panelButtonId = useRef('');

let panDelta = 128;
const panDelta = useRef(128);

/**
* Simulate map mouse click to trigger details panel
* @function simulateClick
* @param {KeyboardEvent} evt the keyboard event
* @param {KeyboardEvent} event the keyboard event
*/
function simulateClick(evt: KeyboardEvent): void {
if (evt.key === 'Enter') {
if (isCrosshairsActiveRef.current) {
// updater store with the lnglat point
const mapClickCoordinatesFetch: TypeMapMouseInfo = {
projected: [0, 0],
pixel: [0, 0],
lnglat: toLonLat(mapCoordRef.current, `EPSG:${projection}`),
dragging: false,
};
store.setState({
mapState: { ...store.getState().mapState, clickCoordinates: mapClickCoordinatesFetch },
});
}
const simulateClick = useCallback((event: KeyboardEvent) => {
if (event.key === 'Enter') {
setClickCoordinates();
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

/**
* Modify the pixelDelta value for the keyboard pan on Shift arrow up or down
*
* @param {KeyboardEvent} evt the keyboard event to trap
* @param {KeyboardEvent} event the keyboard event to trap
*/
function managePanDelta(evt: KeyboardEvent): void {
if ((evt.key === 'ArrowDown' && evt.shiftKey) || (evt.key === 'ArrowUp' && evt.shiftKey)) {
panDelta = evt.key === 'ArrowDown' ? (panDelta -= 10) : (panDelta += 10);
panDelta = panDelta < 10 ? 10 : panDelta; // minus panDelta reset the value so we need to trap
const managePanDelta = useCallback((event: KeyboardEvent) => {
if ((event.key === 'ArrowDown' && event.shiftKey) || (event.key === 'ArrowUp' && event.shiftKey)) {
panDelta.current = event.key === 'ArrowDown' ? (panDelta.current -= 10) : (panDelta.current += 10);
panDelta.current = panDelta.current < 10 ? 10 : panDelta.current; // minus panDelta reset the value so we need to trap

// replace the KeyboardPan interraction by a new one
// const mapElement = mapElementRef.current;
mapElement.getInteractions().forEach((interactionItem) => {
if (interactionItem instanceof KeyboardPan) {
mapElement.removeInteraction(interactionItem);
}
});
mapElement.addInteraction(new KeyboardPan({ pixelDelta: panDelta }));
setMapKeyboardPanInteractions(panDelta.current);
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

useEffect(() => {
const unsubIsCrosshair = getGeoViewStore(mapId).subscribe(
(state) => state.appState.isCrosshairsActive,
(curCrosshair, prevCrosshair) => {
if (curCrosshair !== prevCrosshair) {
const mapHTMLElement = mapElement.getTargetElement();

if (curCrosshair) {
panelButtonId.current = 'detailsPanel';

mapHTMLElement.addEventListener('keydown', simulateClick);
mapHTMLElement.addEventListener('keydown', managePanDelta);
} else {
mapHTMLElement.removeEventListener('keydown', simulateClick);
mapHTMLElement.removeEventListener('keydown', managePanDelta);
}
}
}
);
const mapHTMLElement = mapElement.getTargetElement();
if (isCrosshairsActive) {
panelButtonId.current = 'detailsPanel';
mapHTMLElement.addEventListener('keydown', simulateClick);
mapHTMLElement.addEventListener('keydown', managePanDelta);
} else {
mapHTMLElement.removeEventListener('keydown', simulateClick);
mapHTMLElement.removeEventListener('keydown', managePanDelta);
}

return () => {
const mapHTMLElement = mapElement.getTargetElement();
unsubIsCrosshair();
mapHTMLElement.removeEventListener('keydown', simulateClick);
mapHTMLElement.removeEventListener('keydown', managePanDelta);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
}, [isCrosshairsActive, mapElement, simulateClick, managePanDelta]);

return (
<Box
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { MouseEventHandler, useContext } from 'react';
import { useTranslation } from 'react-i18next';
import { Button, Dialog, DialogActions, DialogTitle } from '@mui/material';
import { Button, Dialog, DialogActions, DialogTitle } from '@mui/material'; // TODO: refactor to use UI

import { MapContext } from '@/core/app-start';
import { exportPNG } from '../../utils/utilities';
import { exportPNG } from '@/core/utils/utilities';

/**
* Interface used for home button properties
Expand All @@ -28,10 +28,12 @@ const defaultProps = {
*/
export default function ExportModal(props: ExportModalProps): JSX.Element {
const { className, isShown, closeModal } = props;
const { t } = useTranslation();

const mapConfig = useContext(MapContext);
const { mapId } = mapConfig;

const { t } = useTranslation();

return (
<Dialog open={isShown} onClose={closeModal} className={className}>
<form
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
import { MutableRefObject, useCallback, useContext, useEffect, useRef, useState } from 'react';
import { Box, useTheme } from '@mui/material';

import { MapContext } from '@/core/app-start';
import { api } from '@/app';

import { EVENT_NAMES } from '@/api/events/event-types';
import { FooterTabPayload, PayloadBaseClass, payloadIsAFooterTab } from '@/api/events/payloads';
import { useTheme } from '@mui/material/styles';

import {
Box,
ExpandLessIcon,
ExpandMoreIcon,
FullscreenIcon,
Expand All @@ -19,6 +15,10 @@ import {
MoveUpRoundedIcon,
ArrowUpIcon,
} from '@/ui';
import { MapContext } from '@/core/app-start';
import { api } from '@/app';
import { EVENT_NAMES } from '@/api/events/event-types';
import { FooterTabPayload, PayloadBaseClass, payloadIsAFooterTab } from '@/api/events/payloads';
import { getSxClasses } from './footer-tabs-style';

/**
Expand Down
20 changes: 3 additions & 17 deletions packages/geoview-core/src/core/components/geolocator/geo-list.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,13 @@
import { ListItem } from '@mui/material'; // TODO because of forward ref problem we can't use it inside the tooltip if provided by UI
import { ListItemButton, Grid, Tooltip, Typography } from '@/ui';
import { GeoListItem } from './geolocator';
import { sxClassesList } from './geolocator-style';

type GeoListProps = {
geoListItems: GeoListItem[];
zoomToLocation: (coords: [number, number], bbox: [number, number, number, number]) => void;
};

const sxClasses = {
main: {
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
'& span': {
fontSize: '0.75rem',
':first-of-type': {
fontWeight: 'bold',
fontSize: '0.875rem',
},
},
},
};

type tooltipProp = Pick<GeoListItem, 'name' | 'tag' | 'province'>;

/**
Expand Down Expand Up @@ -70,7 +56,7 @@ export default function GeoList({ geoListItems, zoomToLocation }: GeoListProps)
<ListItemButton onClick={() => zoomToLocation([geoListItem.lng, geoListItem.lat], geoListItem.bbox)}>
<Grid container>
<Grid item xs={12} sm={8}>
<Typography component="p" sx={sxClasses.main}>
<Typography component="p" sx={sxClassesList.main}>
<Typography component="span">{geoListItem.name}</Typography>
{!!geoListItem.tag && geoListItem.tag.length && !!geoListItem.tag[0] && (
<Typography component="span">{`, ${geoListItem.tag[0]}`}</Typography>
Expand All @@ -82,7 +68,7 @@ export default function GeoList({ geoListItems, zoomToLocation }: GeoListProps)
</Grid>
<Grid item xs={12} sm={4} sx={{ textAlign: 'right' }}>
{!!geoListItem.tag && geoListItem.tag.length > 1 && !!geoListItem.tag[1] && (
<Typography component="p" sx={sxClasses.main}>
<Typography component="p" sx={sxClassesList.main}>
<Typography component="span"> {geoListItem.tag[1]}</Typography>
</Typography>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,21 @@ export const sxClasses = {
},
};

export const sxClassesList = {
main: {
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
'& span': {
fontSize: '0.75rem',
':first-of-type': {
fontWeight: 'bold',
fontSize: '0.875rem',
},
},
},
};

export const StyledInputField = styled(Input)(({ theme }) => ({
color: 'inherit',
width: '100%',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export function Geolocator() {

const urlRef = useRef<string>(`${serviceUrls!.geolocator}&lang=${i18n.language}`);

// internal state
const [data, setData] = useState<GeoListItem[]>();
const [error, setError] = useState<Error>();
const [isLoading, setIsLoading] = useState<boolean>(false);
Expand Down
27 changes: 4 additions & 23 deletions packages/geoview-core/src/core/components/map/map.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,7 @@ import { Extent } from 'ol/extent';
import { Box, useMediaQuery } from '@mui/material';
import { useTheme } from '@mui/material/styles';

import { useStore } from 'zustand';
import { XYZ } from 'ol/source';
import { getGeoViewStore } from '@/core/stores/stores-managers';

import { NorthArrow, NorthPoleFlag } from '@/core/components/north-arrow/north-arrow';
import { Crosshair } from '@/core/components/crosshair/crosshair';
Expand All @@ -33,6 +31,7 @@ import { MapViewer } from '@/geo/map/map-viewer';
import { payloadIsABasemapLayerArray, payloadIsAMapViewProjection, PayloadBaseClass } from '@/api/events/payloads';
import { TypeBasemapProps, TypeMapFeaturesConfig } from '../../types/global-types';
import { sxClasses } from './map-style';
import { useMapLoaded, useMapNorthArrow, useMapOverviewMap } from '@/core/stores/store-interface-and-intial-values/map-state';

export function Map(mapFeaturesConfig: TypeMapFeaturesConfig): JSX.Element {
const { map: mapConfig } = mapFeaturesConfig;
Expand All @@ -47,9 +46,9 @@ export function Map(mapFeaturesConfig: TypeMapFeaturesConfig): JSX.Element {
const [overviewBaseMap, setOverviewBaseMap] = useState<TypeBasemapProps | undefined>(undefined);

// get values from the store
const overviewMap = useStore(getGeoViewStore(mapId), (state) => state.mapState.overviewMap);
const northArrow = useStore(getGeoViewStore(mapId), (state) => state.mapState.northArrow);
const mapLoaded = useStore(getGeoViewStore(mapId), (state) => state.mapState.mapLoaded);
const overviewMap = useMapOverviewMap();
const northArrow = useMapNorthArrow();
const mapLoaded = useMapLoaded();

// create a new map viewer instance
const viewer: MapViewer = api.maps[mapId];
Expand All @@ -59,24 +58,6 @@ export function Map(mapFeaturesConfig: TypeMapFeaturesConfig): JSX.Element {
// if screen size is medium and up
const deviceSizeMedUp = useMediaQuery(defaultTheme.breakpoints.up('md'));

// TODO: do not deal with stuff not related to create the payload in the event, use the event on or store state to listen to change and do what is needed.
// !This was in mapZoomEnd event.... listen to the event in proper place
// Object.keys(layers).forEach((layer) => {
// if (layer.endsWith('-unclustered')) {
// const clusterLayerId = layer.replace('-unclustered', '');
// const splitZoom =
// (api.maps[mapId].layer.registeredLayers[clusterLayerId].source as TypeVectorSourceInitialConfig)!.cluster!.splitZoom || 7;
// if (prevZoom < splitZoom && currentZoom >= splitZoom) {
// api.maps[mapId].layer.registeredLayers[clusterLayerId]?.olLayer!.setVisible(false);
// api.maps[mapId].layer.registeredLayers[layer]?.olLayer!.setVisible(true);
// }
// if (prevZoom >= splitZoom && currentZoom < splitZoom) {
// api.maps[mapId].layer.registeredLayers[clusterLayerId]?.olLayer!.setVisible(true);
// api.maps[mapId].layer.registeredLayers[layer]?.olLayer!.setVisible(false);
// }
// }
// });

const initCGPVMap = (cgpvMap: OLMap) => {
cgpvMap.set('mapId', mapId);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { useContext } from 'react';
import { useTheme } from '@mui/material/styles';

import { MapContext } from '@/core/app-start';
import { api } from '@/app';
import { IconButton, FullscreenIcon, FullscreenExitIcon } from '@/ui';
import { TypeHTMLElement } from '@/core/types/global-types';
import { getSxClasses } from '../nav-bar-style';
Expand Down Expand Up @@ -50,8 +49,7 @@ export default function Fullscreen(): JSX.Element {
const element = document.getElementById(`shell-${mapId}`);

if (element) {
setFullScreenActive(!isFullScreen);
api.maps[mapId].toggleFullscreen(!isFullScreen, element as TypeHTMLElement);
setFullScreenActive(!isFullScreen, element as TypeHTMLElement);

// if state will become fullscreen, add event listerner to trap exit by ESC key
// put a timeout for the toggle to fullscreen to happen
Expand Down
Loading

0 comments on commit 79ec7bb

Please sign in to comment.