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 position of hover tooltip + Layer fixes #2159

Merged
Merged
Show file tree
Hide file tree
Changes from all 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
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import React, { useEffect, useState } from 'react';
import React, { useEffect, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useTheme, Theme } from '@mui/material/styles';

import { Box } from '@/ui';
import { logger } from '@/core/utils/logger';
import { useMapHoverFeatureInfo, useMapPointerPosition } from '@/core/stores/store-interface-and-intial-values/map-state';
import { getSxClasses } from './hover-tooltip-styles';
import { useGeoViewMapId } from '@/core/stores/geoview-store';
import { useAppGeoviewHTMLElement } from '@/core/stores/store-interface-and-intial-values/app-state';

/**
* Hover tooltip component to show name field information on hover
Expand All @@ -17,13 +19,13 @@ export function HoverTooltip(): JSX.Element | null {
// logger.logTraceRender('components/hover-tooltip/hover-tooltip');

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

const theme: Theme & {
iconImage: React.CSSProperties;
} = useTheme();

// internal component state
const [pixel, setPixel] = useState<[number, number]>([0, 0]);
const [tooltipValue, setTooltipValue] = useState<string>('');
const [tooltipIcon, setTooltipIcon] = useState<string>('');
const [showTooltip, setShowTooltip] = useState<boolean>(false);
Expand All @@ -33,6 +35,9 @@ export function HoverTooltip(): JSX.Element | null {
// store state
const hoverFeatureInfo = useMapHoverFeatureInfo();
const pointerPosition = useMapPointerPosition();
const mapElem = useAppGeoviewHTMLElement().querySelector(`[id^="mapTargetElement-${mapId}"]`) as HTMLElement;

const tooltipRef = useRef<HTMLDivElement>(null);

// Update tooltip when store value change from propagation by hover-layer-set to map-event-processor
useEffect(() => {
Expand All @@ -54,19 +59,44 @@ export function HoverTooltip(): JSX.Element | null {
setTooltipValue('');
setTooltipIcon('');
setShowTooltip(false);

if (pointerPosition !== undefined) setPixel(pointerPosition.pixel as [number, number]);
}, [pointerPosition]);

// Update tooltip position when we have the dimensions of the tooltip
useEffect(() => {
logger.logTraceUseEffect('HOVER-TOOLTIP - tooltipValue changed', tooltipValue);

if (!mapElem || !tooltipRef.current || !pointerPosition || !pointerPosition.pixel || !tooltipValue) {
return;
}

const mapRect = mapElem.getBoundingClientRect();
const tooltipRect = tooltipRef.current.getBoundingClientRect();

// Check if the tooltip is outside the map
let tooltipX = pointerPosition.pixel[0];
let tooltipY = pointerPosition.pixel[1] - 35;

if (pointerPosition.pixel[0] + tooltipRect.width > mapRect.width) {
tooltipX = pointerPosition.pixel[0] - tooltipRect.width;
}

if (pointerPosition.pixel[1] - tooltipRect.height < mapRect.top) {
tooltipY = pointerPosition.pixel[1] + 10;
}

tooltipRef.current.style.left = `${tooltipX}px`;
tooltipRef.current.style.top = `${tooltipY}px`;
}, [tooltipValue, mapElem, pointerPosition]);

if (showTooltip && !tooltipValue) {
return null;
}

return (
<Box
ref={tooltipRef}
sx={sxClasses.tooltipItem}
style={{
transform: `translate(${pixel[0]}px, ${pixel[1] - 35}px)`,
visibility: showTooltip ? 'visible' : 'hidden',
}}
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
VisibilityOutlinedIcon,
RestartAltIcon,
Paper,
Typography,
} from '@/ui';
import { TypeLegendLayer } from '@/core/components/layers/types';
import {
Expand All @@ -37,7 +38,6 @@
import { LAYER_STATUS } from '@/core/utils/constant';
import { ArrowDownwardIcon, ArrowUpIcon, TableViewIcon } from '@/ui/icons';
import { Divider } from '@/ui/divider/divider';
import { Box } from '@/ui/layout';

interface SingleLayerProps {
layer: TypeLegendLayer;
Expand Down Expand Up @@ -124,10 +124,10 @@

if (datatableSettings[layer.layerPath]) {
return (
<Box component="span">
<Typography sx={{ color: 'unset', fontSize: 'unset' }} component="span">
{itemsLengthDesc} &nbsp;
<TableViewIcon sx={{ marginBottom: '-5px' }} fontSize="small" />
</Box>
</Typography>
);
}
return itemsLengthDesc;
Expand Down Expand Up @@ -156,7 +156,7 @@
!layerData.filter((layers) => layers.layerPath === layer.layerPath && !!layers?.features?.length).length ||
layer.layerStatus === LAYER_STATUS.ERROR
) {
triggerGetAllFeatureInfo(layer.layerPath);

Check warning on line 159 in packages/geoview-core/src/core/components/layers/left-panel/single-layer.tsx

View workflow job for this annotation

GitHub Actions / Build demo files / build-geoview

Promises must be awaited, end with a call to .catch, end with a call to .then with a rejection handler or be explicitly marked as ignored with the `void` operator
}
}
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,7 @@ export function LayerDetails(props: LayerDetailsProps): JSX.Element {
sx={{
marginTop: '10px',
color: theme.palette.geoViewColor.textColor.light[200],
fontSize: theme.palette.geoViewFontSize.xs,
fontSize: theme.palette.geoViewFontSize.sm,
textAlign: 'center',
}}
key={generateId()}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { useState } from 'react';
import { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useTheme } from '@mui/material/styles';
import _ from 'lodash';
import { ClickAwayListener } from '@mui/material';
import { animated, useSpring } from '@react-spring/web';
import {
Box,
InfoIcon,
Expand All @@ -12,6 +13,7 @@ import {
CloseIcon,
IconButton,
NotificationsIcon,
NotificationsActiveIcon,
Badge,
Typography,
Popper,
Expand Down Expand Up @@ -51,13 +53,32 @@ export default function Notifications(): JSX.Element {

// internal state
const [anchorEl, setAnchorEl] = useState<HTMLButtonElement | null>(null);
const [hasNewNotification, setHasNewNotification] = useState(false);
const [notificationsCount, setNotificationsCount] = useState(0);
const [open, setOpen] = useState(false);

// get values from the store
const notifications = useAppNotifications();
const interaction = useMapInteraction();
const { removeNotification } = useAppStoreActions();
const notificationsCount = _.sumBy(notifications, (n) => n.count);

useEffect(() => {
logger.logTraceUseEffect('Notifications - notifications list changed', notificationsCount, notifications);
const curNotificationCount = _.sumBy(notifications, (n) => n.count);
if (curNotificationCount > notificationsCount) {
setHasNewNotification(true);
}
setNotificationsCount(curNotificationCount);
}, [notifications, notificationsCount]);

useEffect(() => {
logger.logTraceUseEffect('Notifications - hasNewNotification change', hasNewNotification);
if (hasNewNotification) {
const timeoutId = setTimeout(() => setHasNewNotification(false), 1000); // Remove after 3 seconds
return () => clearTimeout(timeoutId);
}
return undefined;
}, [hasNewNotification]);

// handle open/close
const handleOpenPopover = (event: React.MouseEvent<HTMLButtonElement>): void => {
Expand All @@ -71,13 +92,26 @@ export default function Notifications(): JSX.Element {
}
};

const shakeAnimation = useSpring({
from: { x: 0, scale: 1 },
to: async (next) => {
await next({ x: 2 }); // Move 10px right and scale up 10%
await next({ x: -2 }); // Move 10px left and scale down 10%
await next({ x: 0 }); // Reset position and scale
},
config: { duration: 50 }, // Adjust duration for faster shake
loop: true,
});

/**
* Remove a notification
*/
const handleRemoveNotificationClick = (notification: NotificationDetailsType): void => {
removeNotification(notification.key);
};

const AnimatedBox = animated(Box);

function getNotificationIcon(notification: NotificationDetailsType): JSX.Element {
switch (notification.notificationType) {
case 'success':
Expand Down Expand Up @@ -122,7 +156,20 @@ export default function Notifications(): JSX.Element {
className={`${interaction === 'dynamic' ? 'style3' : 'style4'} ${open ? 'active' : ''}`}
color="primary"
>
<NotificationsIcon />
{!hasNewNotification && (
<Box>
<NotificationsIcon />
</Box>
)}
{hasNewNotification && (
<AnimatedBox
style={{
...shakeAnimation,
}}
>
<NotificationsActiveIcon />
</AnimatedBox>
)}
</IconButton>
</Badge>

Expand Down
Loading