From daa86d7b85fcc8eef9257f632db8ca34a0bf7681 Mon Sep 17 00:00:00 2001
From: izawartka <59137928+izawartka@users.noreply.github.com>
Date: Thu, 13 Aug 2026 19:02:15 +0200
Subject: [PATCH] Extract TrackHoverInfoTable component
- Extracted TrackHoverInfoTable as a separate component
---
.../map/track-hover-info/TrackHoverInfo.css | 8 +-
.../map/track-hover-info/TrackHoverInfo.jsx | 36 ++++++--
.../track-hover-info/TrackHoverInfoPopup.jsx | 84 +------------------
.../track-hover-info/TrackHoverInfoTable.jsx | 82 ++++++++++++++++++
4 files changed, 116 insertions(+), 94 deletions(-)
create mode 100644 src/components/map/track-hover-info/TrackHoverInfoTable.jsx
diff --git a/src/components/map/track-hover-info/TrackHoverInfo.css b/src/components/map/track-hover-info/TrackHoverInfo.css
index b930e31..b6e71d8 100644
--- a/src/components/map/track-hover-info/TrackHoverInfo.css
+++ b/src/components/map/track-hover-info/TrackHoverInfo.css
@@ -12,16 +12,16 @@
padding: 1em;
}
-.track-hover-info-popup svg {
+.track-hover-info-popup-table svg {
width: 1.2em;
height: 1.2em;
}
-.track-hover-info-popup table {
+.track-hover-info-popup-table {
border-spacing: 2px;
}
-.track-hover-info-popup th {
+.track-hover-info-popup-table th {
text-align: start;
padding-right: 2em;
color: var(--text-color-disabled);
@@ -33,7 +33,7 @@
color: var(--text-color-disabled);
}
-.track-hover-info-popup .track-hover-info-popup-label {
+.track-hover-info-popup-table .track-hover-info-popup-label {
display: flex;
align-items: center;
gap: 0.5em;
diff --git a/src/components/map/track-hover-info/TrackHoverInfo.jsx b/src/components/map/track-hover-info/TrackHoverInfo.jsx
index eb215f6..2b858fa 100644
--- a/src/components/map/track-hover-info/TrackHoverInfo.jsx
+++ b/src/components/map/track-hover-info/TrackHoverInfo.jsx
@@ -1,8 +1,10 @@
-import { useState, useEffect, useCallback, useRef, useContext } from "react";
-import { hoveredTrack$ } from "../../../services/trackHoverInfoService";
+import { useState, useEffect, useCallback, useRef, useContext, useMemo } from "react";
+import { hoveredTrack$, touchClickedTrack$ } from "../../../services/trackHoverInfoService";
import TrackHoverInfoPopup from "./TrackHoverInfoPopup";
import SettingsContext from "../../../contexts/SettingsContext";
+import { showDialog } from "../../../services/dialogService";
import './TrackHoverInfo.css';
+import TrackHoverInfoPopupTable from "./TrackHoverInfoTable";
export default function TrackHoverInfo() {
const { showTrackHoverInfo } = useContext(SettingsContext);
@@ -18,6 +20,7 @@ function InnerTrackHoverInfo() {
const [ hoveredTrack, setHoveredTrack ] = useState(null);
const mousePos = useRef({ x: 0, y: 0 });
const divRef = useRef(null);
+ const isMobile = useMemo(() => window.matchMedia('(pointer: coarse)').matches, []);
const handleMouseMove = useCallback((event) => {
mousePos.current.x = event.clientX;
@@ -26,19 +29,34 @@ function InnerTrackHoverInfo() {
if (!divRef.current) return;
divRef.current.style.left = `${mousePos.current.x}px`;
divRef.current.style.top = `${mousePos.current.y}px`;
- }, [mousePos, divRef]);
+ }, []);
useEffect(() => {
- const subscription = hoveredTrack$.subscribe(date => {
- setHoveredTrack(date);
+ const hoveredTrackSub = hoveredTrack$.subscribe(track => {
+ if (isMobile) return;
+ setHoveredTrack(track);
});
- document.addEventListener('mousemove', handleMouseMove);
+ const touchClickedTrackSub = touchClickedTrack$.subscribe(track => {
+ if (!isMobile || !track) return;
+
+ showDialog(
+ ,
+ [{ text: 'Close', onClick: () => {}, autoFocus: true, bgClick: true }]
+ );
+ });
+
+ if (!isMobile) {
+ document.addEventListener('mousemove', handleMouseMove);
+ }
return () => {
- subscription.unsubscribe();
- document.removeEventListener('mousemove', handleMouseMove);
- }
+ hoveredTrackSub.unsubscribe();
+ touchClickedTrackSub.unsubscribe();
+ if (!isMobile) {
+ document.removeEventListener('mousemove', handleMouseMove);
+ }
+ };
}, [handleMouseMove]);
if (!hoveredTrack) return null;
diff --git a/src/components/map/track-hover-info/TrackHoverInfoPopup.jsx b/src/components/map/track-hover-info/TrackHoverInfoPopup.jsx
index 80ff36f..990d08e 100644
--- a/src/components/map/track-hover-info/TrackHoverInfoPopup.jsx
+++ b/src/components/map/track-hover-info/TrackHoverInfoPopup.jsx
@@ -1,35 +1,12 @@
-import Constants from "../../../helpers/constants";
-import { QuestionMarkIcon, SlopeIcon, SpeedIcon, HashIcon, LightningIcon, RadiusIcon } from "../../../icons";
import { TrackShape } from "../../../model/tracks/track";
-import TrackHoverInfoConsts from "./TrackHoverInfoConsts";
+import TrackHoverInfoPopupTable from "./TrackHoverInfoTable";
-export default function TrackHoverInfoPopup(props) {
- const { track } = props;
-
- const slopeValue = Array.from(new Set([
- Math.abs(track.start_slope).toFixed(1),
- Math.abs(track.end_slope).toFixed(1),
- ]));
- const slopeOptions = {join: ' / ', subOptions: {suffix: ' ‰'}};
+export default function TrackHoverInfoPopup({track}) {
const showAlignHint = track.shape === TrackShape.STRAIGHT;
return (
-
-
- {Constants.map.showDebugTrackIds && (
-
- )}
-
-
-
-
-
-
-
-
-
-
+
{showAlignHint && (
Double click to align the view
@@ -38,58 +15,3 @@ export default function TrackHoverInfoPopup(props) {
);
}
-
-function InfoPopupSwitchItems(props) {
- const { track } = props;
- if (!track.switch) return null;
-
- return <>
-
-
- >
-}
-
-function InfoPopupShapeItems(props) {
- const { track } = props;
- if (track.r === 0) return null;
-
- return
-}
-
-function processItemValue(value, options) {
- const { translationConstKey, join, subOptions, suffix } = options || {};
-
- if (Array.isArray(value)) {
- return value.map(item => processItemValue(item, subOptions)).join(join || ", ");
- }
-
- if (translationConstKey) {
- return TrackHoverInfoConsts[translationConstKey]?.[value] || value;
- }
-
- if (typeof value === 'number' && 'precision' in options) {
- value = value.toFixed(options.precision);
- }
-
- if (value === undefined || value === null || value === '') {
- return "N/A";
- }
-
- return `${value}${suffix || ''}`.trim();
-}
-
-function InfoPopupItem({ icon: Icon, label, value, options = {} }) {
- const textValue = processItemValue(value, options);
-
- return (
-
- |
-
- {Icon ? : null}
- {label}
-
- |
- {textValue} |
-
- );
-}
diff --git a/src/components/map/track-hover-info/TrackHoverInfoTable.jsx b/src/components/map/track-hover-info/TrackHoverInfoTable.jsx
new file mode 100644
index 0000000..62c177c
--- /dev/null
+++ b/src/components/map/track-hover-info/TrackHoverInfoTable.jsx
@@ -0,0 +1,82 @@
+import Constants from "../../../helpers/constants";
+import { QuestionMarkIcon, SlopeIcon, SpeedIcon, HashIcon, LightningIcon, RadiusIcon } from "../../../icons";
+import TrackHoverInfoConsts from "./TrackHoverInfoConsts";
+
+export default function TrackHoverInfoPopupTable({track}) {
+ const slopeValue = Array.from(new Set([
+ Math.abs(track.start_slope).toFixed(1),
+ Math.abs(track.end_slope).toFixed(1),
+ ]));
+ const slopeOptions = {join: ' / ', subOptions: {suffix: ' ‰'}};
+
+ return (
+
+
+ {Constants.map.showDebugTrackIds && (
+
+ )}
+
+
+
+
+
+
+
+
+
+
+ );
+}
+
+function InfoPopupSwitchItems({track}) {
+ if (!track.switch) return null;
+
+ return <>
+
+
+ >
+}
+
+function InfoPopupShapeItems({track}) {
+ if (track.r === 0) return null;
+
+ return
+}
+
+function processItemValue(value, options) {
+ const { translationConstKey, join, subOptions, suffix } = options || {};
+
+ if (Array.isArray(value)) {
+ return value.map(item => processItemValue(item, subOptions)).join(join || ", ");
+ }
+
+ if (translationConstKey) {
+ return TrackHoverInfoConsts[translationConstKey]?.[value] || value;
+ }
+
+ if (typeof value === 'number' && 'precision' in options) {
+ value = value.toFixed(options.precision);
+ }
+
+ if (value === undefined || value === null || value === '') {
+ return "N/A";
+ }
+
+ return `${value}${suffix || ''}`.trim();
+}
+
+function InfoPopupItem({ icon: Icon, label, value, options = {} }) {
+ const textValue = processItemValue(value, options);
+
+ return (
+
+ |
+
+ {Icon ? : null}
+ {label}
+
+ |
+ {textValue} |
+
+ );
+}