diff --git a/package-lock.json b/package-lock.json index 7c6e0b8cd78..37d9afbdf55 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "geoview", - "lockfileVersion": 2, + "lockfileVersion": 3, "requires": true, "packages": {} } diff --git a/packages/geoview-core/src/core/components/hover-tooltip/hover-tooltip.tsx b/packages/geoview-core/src/core/components/hover-tooltip/hover-tooltip.tsx index d400e9f65c4..e78c82ef227 100644 --- a/packages/geoview-core/src/core/components/hover-tooltip/hover-tooltip.tsx +++ b/packages/geoview-core/src/core/components/hover-tooltip/hover-tooltip.tsx @@ -1,4 +1,4 @@ -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'; @@ -6,6 +6,8 @@ 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 @@ -17,13 +19,13 @@ export function HoverTooltip(): JSX.Element | null { // logger.logTraceRender('components/hover-tooltip/hover-tooltip'); const { t } = useTranslation(); + 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(''); const [tooltipIcon, setTooltipIcon] = useState(''); const [showTooltip, setShowTooltip] = useState(false); @@ -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(null); // Update tooltip when store value change from propagation by hover-layer-set to map-event-processor useEffect(() => { @@ -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 ( diff --git a/packages/geoview-core/src/core/components/layers/left-panel/single-layer.tsx b/packages/geoview-core/src/core/components/layers/left-panel/single-layer.tsx index e6754719c37..ba7bc6c1d71 100644 --- a/packages/geoview-core/src/core/components/layers/left-panel/single-layer.tsx +++ b/packages/geoview-core/src/core/components/layers/left-panel/single-layer.tsx @@ -17,6 +17,7 @@ import { VisibilityOutlinedIcon, RestartAltIcon, Paper, + Typography, } from '@/ui'; import { TypeLegendLayer } from '@/core/components/layers/types'; import { @@ -37,7 +38,6 @@ import { 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; @@ -124,10 +124,10 @@ export function SingleLayer({ depth, layer, setIsLayersListPanelVisible, index, if (datatableSettings[layer.layerPath]) { return ( - + {itemsLengthDesc}   - + ); } return itemsLengthDesc; diff --git a/packages/geoview-core/src/core/components/layers/right-panel/layer-details.tsx b/packages/geoview-core/src/core/components/layers/right-panel/layer-details.tsx index 3a42b64d1f0..02c0b89b8b5 100644 --- a/packages/geoview-core/src/core/components/layers/right-panel/layer-details.tsx +++ b/packages/geoview-core/src/core/components/layers/right-panel/layer-details.tsx @@ -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()} diff --git a/packages/geoview-core/src/core/components/notifications/notifications.tsx b/packages/geoview-core/src/core/components/notifications/notifications.tsx index dae66dbe8c5..f625a795de7 100644 --- a/packages/geoview-core/src/core/components/notifications/notifications.tsx +++ b/packages/geoview-core/src/core/components/notifications/notifications.tsx @@ -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, @@ -12,6 +13,7 @@ import { CloseIcon, IconButton, NotificationsIcon, + NotificationsActiveIcon, Badge, Typography, Popper, @@ -51,13 +53,32 @@ export default function Notifications(): JSX.Element { // internal state const [anchorEl, setAnchorEl] = useState(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): void => { @@ -71,6 +92,17 @@ 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 */ @@ -78,6 +110,8 @@ export default function Notifications(): JSX.Element { removeNotification(notification.key); }; + const AnimatedBox = animated(Box); + function getNotificationIcon(notification: NotificationDetailsType): JSX.Element { switch (notification.notificationType) { case 'success': @@ -122,7 +156,20 @@ export default function Notifications(): JSX.Element { className={`${interaction === 'dynamic' ? 'style3' : 'style4'} ${open ? 'active' : ''}`} color="primary" > - + {!hasNewNotification && ( + + + + )} + {hasNewNotification && ( + + + + )}