Skip to content

Commit ca12733

Browse files
committed
🛠️ [fix] Enhances UI and fixes map issues
This commit improves the user interface and fixes several map-related issues. - 🛠️ [fix] Corrects elapsed time display in tooltips. - Changes "Ride Time" to "Elapsed Time" for clarity. - 🛠️ [fix] Fixes file name sanitization for GPX exports. - Uses `replaceAll` instead of `replace` to remove all invalid characters from track names, ensuring valid file names for GPX exports. ⚙️ - 🛠️ [fix] Preserves active tab during file loading. - Saves and restores the current tab when loading overlay files to prevent unexpected tab switches. 🔄 - 🗺️ [feat] Improves map tab activation and centering. - Ensures the map tab is always activated and the map is centered on the main file when the active file name is clicked. 🖱️ - Centers the map after a delay to ensure the tab switch completes. - 🗺️ [feat] Enhances mini-map functionality. - Adds configuration options for better control like `zoomLevelOffset`, `aimingRectOptions`, `shadowRectOptions`, etc. - Forces mini-map to update after a short delay to ensure proper rendering. ⏱️ - Keeps mini-map in sync with the main map by invalidating its size on main map move or zoom events, preventing grey tiles. 🗺️ - 🗺️ [feat] Fixes grey tiles issue on map tab switch. - Invalidates the map size when switching to the map tab to prevent grey tiles from appearing. ⬜ - 🧹 [chore] Reorders imports in `renderer.js` for better organization. 🗂️ - Moves `setupCreditsMarquee` import to improve code readability. Signed-off-by: Nick2bad4u <[email protected]>
1 parent fc58da9 commit ca12733

File tree

10 files changed

+204
-32
lines changed

10 files changed

+204
-32
lines changed

electron-app/renderer.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,10 +80,10 @@
8080
import { setLoading } from "./utils/app/initialization/rendererUtils.js";
8181
// Avoid static imports for modules that tests mock; resolve dynamically via ensureCoreModules()
8282
import { createExportGPXButton } from "./utils/files/export/createExportGPXButton.js";
83-
import { setupCreditsMarquee } from "./utils/ui/layout/enhanceCreditsSection.js";
8483
// Avoid static import of AppActions because tests sometimes mock the module
8584
// Without exporting the named symbol. Always resolve via ensureCoreModules().
8685
import { getState, subscribe } from "./utils/state/core/stateManager.js";
86+
import { setupCreditsMarquee } from "./utils/ui/layout/enhanceCreditsSection.js";
8787
// Import domain-level appState for tests that mock this path explicitly
8888
// Note: app domain state functions are dynamically imported via ensureCoreModules()
8989
// Avoid static import of uiStateManager for the same reason as AppActions in tests

electron-app/style.css

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1324,23 +1324,16 @@ body.theme-light .tab-button.active::after {
13241324

13251325
.app-pill > * {
13261326
position: relative;
1327-
z-index: 1;
1327+
z-index: 9999;
13281328
}
13291329

13301330
.pill-accent {
13311331
color: rgb(var(--color-accent-rgb));
13321332
letter-spacing: 0.18em;
13331333
font-weight: 700;
1334+
overflow: overlay !important;
13341335
}
13351336

1336-
.app-pill:nth-child(2n) {
1337-
animation: pill-breathe 6s ease-in-out infinite;
1338-
}
1339-
1340-
.app-pill:nth-child(2n + 1) {
1341-
animation: pill-breathe 6s ease-in-out infinite;
1342-
animation-delay: 1.5s;
1343-
}
13441337

13451338
body.app-has-file .app-header,
13461339
body[data-has-fit-file="true"] .app-header {
@@ -1428,6 +1421,9 @@ body[data-has-fit-file="true"] .header-bar::after {
14281421
animation-duration: 18s;
14291422
}
14301423

1424+
.leaflet-touch .leaflet-control-measure .leaflet-control-measure-toggle, .leaflet-touch .leaflet-control-measure .leaflet-control-measure-toggle:hover {
1425+
display: none
1426+
}
14311427
body.app-has-file .header-bar #activeFileNameContainer,
14321428
body[data-has-fit-file="true"] .header-bar #activeFileNameContainer {
14331429
flex: 1 1 auto;
@@ -2050,7 +2046,7 @@ body.app-has-file .tab-bar,
20502046
body[data-has-fit-file="true"] .tab-bar {
20512047
margin-top: 0;
20522048
padding-top: 5px;
2053-
margin-bottom: 24px;
2049+
margin-bottom: 0;
20542050
}
20552051

20562052
.tab-bar::after {

electron-app/utils/app/events.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -324,7 +324,7 @@ export function setupListeners({
324324
return;
325325
}
326326

327-
const sanitizedBaseName = trackName.replace(/[\s\u0000-\u001f<>:"/\\|?*]+/gu, "_") || "export";
327+
const sanitizedBaseName = trackName.replaceAll(/[\s\u0000-\u001F<>:"/\\|?*]+/gu, "_") || "export";
328328
const downloadName = filePath.split(/[/\\]/).pop() || `${sanitizedBaseName}.gpx`;
329329
const blob = new Blob([gpx], { type: "application/gpx+xml;charset=utf-8" });
330330
const a = document.createElement("a");

electron-app/utils/app/lifecycle/listeners.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -526,7 +526,7 @@ export function setupListeners({
526526
return;
527527
}
528528

529-
const sanitizedBaseName = trackName.replace(/[\s\u0000-\u001f<>:"/\\|?*]+/gu, "_") || "export";
529+
const sanitizedBaseName = trackName.replaceAll(/[\s\u0000-\u001F<>:"/\\|?*]+/gu, "_") || "export";
530530
const a = document.createElement("a"),
531531
blob = new Blob([gpx], { type: "application/gpx+xml;charset=utf-8" }),
532532
downloadName = safePath.split(/[/\\]/).pop() || `${sanitizedBaseName}.gpx`;

electron-app/utils/files/export/createExportGPXButton.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { getThemeColors } from "../../charts/theming/getThemeColors.js";
2-
import { buildGpxFromRecords, resolveTrackNameFromLoadedFiles } from "./gpxExport.js";
32
import { showNotification } from "../../ui/notifications/showNotification.js";
3+
import { buildGpxFromRecords, resolveTrackNameFromLoadedFiles } from "./gpxExport.js";
44

55
/**
66
* Creates an Export GPX button for exporting the current track as a GPX file.
@@ -31,7 +31,7 @@ export function createExportGPXButton() {
3131
return;
3232
}
3333

34-
const downloadNameBase = trackName.replace(/[\s\u0000-\u001f<>:"/\\|?*]+/gu, "_") || "track";
34+
const downloadNameBase = trackName.replaceAll(/[\s\u0000-\u001F<>:"/\\|?*]+/gu, "_") || "track";
3535
const a = document.createElement("a"),
3636
blob = new Blob([gpx], { type: "application/gpx+xml;charset=utf-8" }),
3737
url = URL.createObjectURL(blob);

electron-app/utils/files/import/loadOverlayFiles.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,13 +66,26 @@ export async function loadOverlayFiles(files) {
6666
}
6767

6868
if (stateDirty) {
69+
// Save current tab before syncing state (which might trigger tab switches)
70+
const currentTabButton = document.querySelector('.tab-button.active');
71+
const currentTabId = currentTabButton?.id;
72+
6973
syncLoadedFitFilesState();
7074
if (globalThis.renderMap) {
7175
globalThis.renderMap();
7276
}
7377
if (/** @type {any} */ (globalThis).updateShownFilesList) {
7478
/** @type {any} */ globalThis.updateShownFilesList();
7579
}
80+
81+
// Restore the original tab if it was changed
82+
if (currentTabId && currentTabButton instanceof HTMLElement) {
83+
const newActiveTab = document.querySelector('.tab-button.active');
84+
if (newActiveTab?.id !== currentTabId) {
85+
console.log('[loadOverlayFiles] Restoring tab to:', currentTabId);
86+
currentTabButton.click();
87+
}
88+
}
7689
}
7790

7891
const finalCount = Array.isArray(globalThis.loadedFitFiles) ? globalThis.loadedFitFiles.length : 0;

electron-app/utils/formatting/display/formatTooltipData.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ export function formatTooltipData(idx, row, lapNum, recordMesgsOverride) {
9393
tooltipParts.push(`<b>Time:</b> ${dateStr}`);
9494
}
9595
if (rideTime) {
96-
tooltipParts.push(`<b>Ride Time:</b> ${rideTime}`);
96+
tooltipParts.push(`<b>Elapsed Time:</b> ${rideTime}`);
9797
}
9898
if (distance) {
9999
tooltipParts.push(`<b>Distance:</b> ${distance.replace("<br>", "")}`);

electron-app/utils/maps/controls/mapActionButtons.js

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -234,17 +234,21 @@ function setupActiveFileNameMapActions() {
234234
try {
235235
console.log("[mapActionButtons] Active file name clicked");
236236

237-
// Switch to map tab if not active
238-
const mapTabBtn = document.querySelector('[data-tab="map"]');
239-
if (mapTabBtn instanceof HTMLElement && !mapTabBtn.classList.contains("active")) {
237+
// Always switch to map tab (even if already active, to ensure map is visible)
238+
const mapTabBtn = document.querySelector('#tab-map');
239+
if (mapTabBtn instanceof HTMLElement) {
240240
console.log("[mapActionButtons] Switching to map tab");
241241
mapTabBtn.click();
242-
}
243242

244-
// Center on main file with a slight delay to ensure tab switch completes
245-
setTimeout(() => {
243+
// Center on main file with a slight delay to ensure tab switch completes
244+
setTimeout(() => {
245+
_centerMapOnMainFile();
246+
}, 100);
247+
} else {
248+
// If map tab button not found, still try to center
249+
console.warn("[mapActionButtons] Map tab button not found, attempting to center anyway");
246250
_centerMapOnMainFile();
247-
}, 100);
251+
}
248252
} catch (error) {
249253
console.error("[mapActionButtons] Error in active filename click:", error);
250254
// Correct argument order: (message, type)

electron-app/utils/maps/core/renderMap.js

Lines changed: 158 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,25 @@ export function renderMap() {
8585
return;
8686
}
8787

88+
// Save drawn items before destroying map
89+
let savedDrawnLayers = [];
90+
if (windowExt._drawnItems && windowExt._drawnItems.getLayers) {
91+
try {
92+
savedDrawnLayers = windowExt._drawnItems.getLayers().map((/** @type {any} */ layer) => ({
93+
geoJSON: layer.toGeoJSON ? layer.toGeoJSON() : null,
94+
options: layer.options,
95+
type: layer instanceof L.Circle ? "circle" :
96+
layer instanceof L.Marker ? "marker" :
97+
layer instanceof L.Polygon ? "polygon" :
98+
layer instanceof L.Polyline ? "polyline" :
99+
layer instanceof L.Rectangle ? "rectangle" : "unknown",
100+
})).filter((item) => item.geoJSON !== null);
101+
console.log("[renderMap] Saved", savedDrawnLayers.length, "drawn items");
102+
} catch (error) {
103+
console.warn("[renderMap] Failed to save drawn items:", error);
104+
}
105+
}
106+
88107
// Fix: Remove any previous Leaflet map instance to avoid grey background bug
89108
if (windowExt._leafletMapInstance && windowExt._leafletMapInstance.remove) {
90109
windowExt._leafletMapInstance.remove();
@@ -367,15 +386,93 @@ export function renderMap() {
367386
// Always use a standard tile layer for the minimap
368387
const miniMapLayer = L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
369388
attribution: "",
389+
maxZoom: 18,
390+
minZoom: 0,
370391
});
371-
new L.Control.MiniMap(miniMapLayer, {
392+
const miniMap = new L.Control.MiniMap(miniMapLayer, {
393+
aimingRectOptions: {
394+
clickable: false,
395+
color: "#ff7800",
396+
fillColor: "#ff7800",
397+
fillOpacity: 0.1,
398+
opacity: 1,
399+
weight: 2,
400+
},
401+
autoToggleDisplay: false,
402+
centerFixed: false,
403+
height: 150,
404+
mapOptions: {
405+
attributionControl: false,
406+
zoomControl: false,
407+
},
408+
minimized: false,
409+
position: "bottomright",
410+
shadowRectOptions: {
411+
clickable: true,
412+
color: "#000000",
413+
fillColor: "#000000",
414+
fillOpacity: 0.2,
415+
opacity: 0.4,
416+
weight: 1,
417+
},
372418
toggleDisplay: true,
373-
}).addTo(map);
419+
width: 150,
420+
zoomAnimation: false,
421+
zoomLevelFixed: false,
422+
zoomLevelOffset: -5,
423+
});
424+
miniMap.addTo(map);
425+
426+
// Force minimap to update after a short delay to ensure proper rendering
427+
setTimeout(() => {
428+
if (miniMap._miniMap) {
429+
miniMap._miniMap.invalidateSize();
430+
}
431+
}, 100);
432+
433+
// Keep minimap in sync when main map moves or zooms to prevent grey tiles
434+
map.on("moveend", () => {
435+
if (miniMap._miniMap) {
436+
miniMap._miniMap.invalidateSize();
437+
}
438+
});
439+
440+
map.on("zoomend", () => {
441+
if (miniMap._miniMap) {
442+
miniMap._miniMap.invalidateSize();
443+
}
444+
});
374445
}
375446

376447
// --- Measurement tool (if plugin available) ---
377448
if (windowExt.L && L.control && L.control.measure) {
378-
L.control.measure({ position: "topleft" }).addTo(map);
449+
const measureControl = L.control.measure({
450+
activeColor: "#ff7800",
451+
captureZIndex: 10_000,
452+
clearMeasurementsOnStop: false,
453+
completedColor: "#1976d2",
454+
decPoint: ".",
455+
popupOptions: {
456+
autoPanPadding: [10, 10],
457+
className: "leaflet-measure-resultpopup",
458+
},
459+
position: "topleft",
460+
primaryAreaUnit: "sqmeters",
461+
primaryLengthUnit: "meters",
462+
secondaryAreaUnit: "acres",
463+
secondaryLengthUnit: "miles",
464+
thousandsSep: ",",
465+
});
466+
measureControl.addTo(map);
467+
windowExt._measureControl = measureControl;
468+
469+
// Clear measurements when starting a new measurement
470+
map.on("measurestart", () => {
471+
// Clear previous completed measurements when starting new one
472+
if (measureControl._measurementRunningTotal) {
473+
measureControl._measurementRunningTotal = 0;
474+
}
475+
});
379476
}
380477

381478
// --- Drawing/editing tool (if plugin available) ---
@@ -384,18 +481,70 @@ export function renderMap() {
384481
map.addLayer(drawnItems);
385482
const drawControl = new L.Control.Draw({
386483
draw: {
387-
circle: true,
484+
circle: {
485+
shapeOptions: {
486+
clickable: true,
487+
color: "#1976d2",
488+
},
489+
},
388490
marker: true,
389-
polygon: true,
390-
polyline: true,
391-
rectangle: true,
491+
polygon: {
492+
allowIntersection: false,
493+
shapeOptions: {
494+
clickable: true,
495+
color: "#1976d2",
496+
},
497+
},
498+
polyline: {
499+
shapeOptions: {
500+
clickable: true,
501+
color: "#1976d2",
502+
},
503+
},
504+
rectangle: {
505+
shapeOptions: {
506+
clickable: true,
507+
color: "#1976d2",
508+
},
509+
},
510+
},
511+
edit: {
512+
edit: true,
513+
featureGroup: drawnItems,
514+
remove: true,
392515
},
393-
edt: false,
394516
});
395517
map.addControl(drawControl);
518+
519+
// Add drawn shapes to the layer so they persist
396520
map.on(L.Draw.Event.CREATED, (/** @type {any} */ e) => {
397-
drawnItems.addLayer(e.layer);
521+
const { layer } = e;
522+
drawnItems.addLayer(layer);
398523
});
524+
525+
// Store reference to drawn items for persistence
526+
windowExt._drawnItems = drawnItems;
527+
528+
// Restore previously drawn items
529+
if (savedDrawnLayers && savedDrawnLayers.length > 0) {
530+
console.log("[renderMap] Restoring", savedDrawnLayers.length, "drawn items");
531+
for (const item of savedDrawnLayers) {
532+
try {
533+
let layer;
534+
if (item.geoJSON) {
535+
layer = L.geoJSON(item.geoJSON, {
536+
onEachFeature: (/** @type {any} */ _feature, /** @type {any} */ createdLayer) => {
537+
drawnItems.addLayer(createdLayer);
538+
},
539+
pointToLayer: (/** @type {any} */ _feature, /** @type {any} */ latlng) => L.marker(latlng),
540+
style: item.options,
541+
});
542+
}
543+
} catch (error) {
544+
console.warn("[renderMap] Failed to restore drawn item:", error);
545+
}
546+
}
547+
}
399548
}
400549

401550
// --- Overlay logic ---

electron-app/utils/ui/tabs/tabStateManager.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -407,6 +407,16 @@ class TabStateManager {
407407
console.log("[TabStateManager] Rendering map for first time");
408408
/** @type {any} */ (globalThis).renderMap();
409409
getStateMgr().setState("map.isRendered", true, { source: "TabStateManager.handleMapTab" });
410+
} else {
411+
// Map already rendered, just invalidate size to fix grey tiles after tab switch
412+
const mapInstance = /** @type {any} */ (globalThis)._leafletMapInstance;
413+
if (mapInstance && typeof mapInstance.invalidateSize === "function") {
414+
// Small delay to ensure DOM has updated
415+
setTimeout(() => {
416+
mapInstance.invalidateSize();
417+
console.log("[TabStateManager] Map size invalidated to fix grey tiles");
418+
}, 50);
419+
}
410420
}
411421
}
412422

0 commit comments

Comments
 (0)