Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
23d8188
Get group visibility info from gpx
Dima-1 Mar 7, 2026
60d11d4
Save point group appearance to the info file
Dima-1 Mar 12, 2026
897e7a3
Fix review
Dima-1 Mar 12, 2026
a58caf7
Move saveInfoFile to separate file
Dima-1 Mar 12, 2026
ec3e7fe
Update appearance differences only
Dima-1 Mar 13, 2026
b431015
Update comments, add empty fallback
Dima-1 Mar 16, 2026
235733e
Update entire info file instead the diff
Dima-1 Mar 18, 2026
098a829
Add info file updatetime in to api request
Dima-1 Mar 18, 2026
9948b8a
Fix inconsistent visibility toggle
Dima-1 Mar 18, 2026
977b0d6
Fix initial wpt group visibility
Dima-1 Mar 18, 2026
9c2c6b4
Fix initial wpt group visibility
Dima-1 Mar 18, 2026
9f0a51f
Move updateGroupsVisibility to manager, fix review
Dima-1 Mar 19, 2026
0512624
Update options and layers
Dima-1 Mar 19, 2026
af37a52
Add test wpt visibility
Dima-1 Mar 20, 2026
5faf155
Remove unnecessary test file, fix formating
Dima-1 Mar 20, 2026
6862054
Merge branch 'main' into hide-wpt-group
alisa911 Mar 20, 2026
2d69854
remove points from info json
alisa911 Mar 20, 2026
49c5a01
fix double gpx ext
alisa911 Mar 20, 2026
6bc34c0
use visible api result
alisa911 Mar 20, 2026
b37c58e
fix update visible wpt groups
alisa911 Mar 21, 2026
7110615
optimize .info sync: skip unchanged uploads, fix missing updatetime o…
alisa911 Mar 23, 2026
6e23d0e
move hidden to top-level group field since it's modified on the web
alisa911 Mar 23, 2026
9edd3e2
Fix massVisible switch state to depend on each group's visibility
Dima-1 Mar 23, 2026
80421cf
Revert "Fix massVisible switch state to depend on each group's visibi…
Dima-1 Mar 23, 2026
5f2e25d
Fix massVisible switch state to depend on each group's visibility
Dima-1 Mar 23, 2026
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
9 changes: 8 additions & 1 deletion map/src/infoblock/components/tabs/TrackTabList.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,14 @@ export default class TrackTabList {
// }

list = list.concat(
Object.keys(tabs).map((item) => <Tab value={tabs[item].key + ''} label={item} key={'tab:' + item} />)
Object.keys(tabs).map((item) => (
<Tab
testid={`se-tab-${item.toLowerCase()}`}
value={tabs[item].key + ''}
label={item}
key={'tab:' + item}
/>
))
);

this.state.tabList = list;
Expand Down
107 changes: 48 additions & 59 deletions map/src/infoblock/components/tabs/WaypointsTab.jsx
Original file line number Diff line number Diff line change
@@ -1,46 +1,48 @@
import { useContext, useEffect, useMemo, useState } from 'react';
import { useContext, useEffect, useMemo, useState, useRef } from 'react';
import AppContext, { isLocalTrack } from '../../../context/AppContext';
import { Alert, Box, Button, Collapse, Grid, IconButton, MenuItem, Switch, Tooltip, Typography } from '@mui/material';
import L from 'leaflet';
import { Cancel, ExpandLess, ExpandMore, KeyboardDoubleArrowDown, KeyboardDoubleArrowUp } from '@mui/icons-material';
import PointManager from '../../../manager/PointManager';
import TracksManager from '../../../manager/track/TracksManager';
import TracksManager, { getResolvedPointsGroups, isWptGroupShown } from '../../../manager/track/TracksManager';
import { confirm } from '../../../dialogs/GlobalConfirmationDialog';
import { useWindowSize } from '../../../util/hooks/useWindowSize';
import { createPoiIcon } from '../../../map/markers/MarkerOptions';
import isEmpty from 'lodash-es/isEmpty';
import { updateGroupsVisibility } from '../../../manager/track/TrackAppearanceManager';

// distinct component
const WaypointGroup = ({ ctx, group, points, defaultOpen, massOpen, massVisible }) => {
const WaypointGroup = ({
ctx,
group,
points,
defaultOpen,
defaultVisible = true,
massOpen,
massVisible,
debouncerTimer,
}) => {
const [open, setOpen] = useState(defaultOpen);
const switchOpen = () => setOpen(!open);

const [visible, setVisible] = useState(true);
const [visible, setVisible] = useState(defaultVisible);

const switchVisible = (e) => {
e.stopPropagation();
setVisible(!visible);
const newVisible = !visible;
setVisible(newVisible);
updateGroupsVisibility(ctx, [group], !newVisible, debouncerTimer);
};

const [mounted, setMounted] = useState(false);
useEffect(() => setMounted(true), []);

// visibility control
useEffect(() => {
mounted &&
points.forEach((p) => {
if (p.layer?._icon?.style) {
p.layer._icon.style.display = visible ? '' : 'none';
}
});
}, [visible]);

useEffect(() => {
mounted && setOpen(massOpen);
}, [massOpen]);

useEffect(() => {
mounted && setVisible(massVisible);
}, [massVisible]);
setVisible(defaultVisible);
}, [defaultVisible, massVisible]);

const point = points[0].wpt;
const iconHTML = createPoiIcon({ point, color: point.color, background: point.background, icon: point.icon })
Expand Down Expand Up @@ -89,7 +91,7 @@ const WaypointGroup = ({ ctx, group, points, defaultOpen, massOpen, massVisible
</Button>
</Grid>
<Grid item xs={2}>
<IconButton onClick={switchVisible}>
<IconButton id={`se-wpt-group-visibility-${group || 'waypoints'}`} onClick={switchVisible}>
<Switch checked={visible} />
</IconButton>
</Grid>
Expand Down Expand Up @@ -245,43 +247,11 @@ export default function WaypointsTab() {
}

function getSortedPoints() {
const wpts = [];

if (ctx.selectedGpxFile.wpts) {
const layers = getLayers();
const wptsMap = Object.fromEntries(
ctx.selectedGpxFile.wpts
.filter((wpt) => wpt.lat != null && wpt.lon != null && !isNaN(wpt.lat) && !isNaN(wpt.lon))
.map((wpt, index) => [
parseFloat(wpt.lat).toFixed(6) + ',' + parseFloat(wpt.lon).toFixed(6),
{ wpt, index },
])
);

layers.forEach((layer) => {
if (layer instanceof L.Marker) {
const coord = layer.getLatLng();
const mapped = wptsMap[coord.lat.toFixed(6) + ',' + coord.lng.toFixed(6)];
mapped && wpts.push({ wpt: mapped.wpt, index: mapped.index, layer });
}
});
}

const az = (a, b) => (a > b) - (a < b);

return wpts.sort((a, b) => {
const aName = a.wpt.name;
const bName = b.wpt.name;

const aCat = a.wpt.category;
const bCat = b.wpt.category;

if (aCat !== bCat) {
return az(aCat, bCat);
}

return az(aName, bName);
});
return (ctx.selectedGpxFile.wpts || [])
.map((wpt, index) => ({ wpt, index }))
.filter(({ wpt }) => wpt.lat != null && wpt.lon != null && !Number.isNaN(wpt.lat) && !Number.isNaN(wpt.lon))
.sort((a, b) => az(a.wpt.category, b.wpt.category) || az(a.wpt.name, b.wpt.name));
}

function getSortedGroups() {
Expand All @@ -299,12 +269,28 @@ export default function WaypointsTab() {
return groups;
}

function isMassVisible() {
const pointsGroups = getResolvedPointsGroups(ctx.selectedGpxFile);
const groupKeys = Object.keys(pointsGroups || {});
return groupKeys.length > 0 && groupKeys.some((g) => isWptGroupShown(pointsGroups, g));
}

const [showMass, setShowMass] = useState(false);
const [massOpen, setMassOpen] = useState(false);
const [massVisible, setMassVisible] = useState(true);
const [massVisible, setMassVisible] = useState(isMassVisible());
const debouncerTimer = useRef(null);

useEffect(() => {
setMassVisible(isMassVisible());
}, [ctx.selectedGpxFile?.info?.pointsGroups]);

const switchMassOpen = () => setMassOpen(!massOpen);
const switchMassVisible = () => setMassVisible(!massVisible);
const switchMassVisible = () => {
const newMassVisible = !massVisible;
setMassVisible(newMassVisible);
const groupNames = Object.keys(getResolvedPointsGroups(ctx.selectedGpxFile) || {});
updateGroupsVisibility(ctx, groupNames, !newMassVisible, debouncerTimer);
};

const pointsChangedString = useMemo(() => {
const name = ctx.selectedGpxFile.name;
Expand All @@ -317,6 +303,7 @@ export default function WaypointsTab() {
const groups = getSortedGroups();
const keys = Object.keys(groups);
const trackName = ctx.selectedGpxFile.name;
const pointsGroups = getResolvedPointsGroups(ctx.selectedGpxFile);

setShowMass(keys.length > 1);

Expand All @@ -329,8 +316,10 @@ export default function WaypointsTab() {
group={g}
points={groups[g]}
defaultOpen={keys.length === 1}
defaultVisible={isWptGroupShown(pointsGroups, g)}
massVisible={massVisible}
massOpen={massOpen}
debouncerTimer={debouncerTimer}
/>
))}
</Box>
Expand All @@ -339,7 +328,7 @@ export default function WaypointsTab() {

return (
<>
<MenuItem divider sx={{ px: 1, py: 1 }}>
<MenuItem id="se-waypoints-tab-content" divider sx={{ px: 1, py: 1 }}>
<Grid container alignItems="center">
<Grid item xs={7}>
{ctx.createTrack && ctx.selectedGpxFile?.wpts && !isEmpty(ctx.selectedGpxFile.wpts) && (
Expand All @@ -364,7 +353,7 @@ export default function WaypointsTab() {
<Grid item xs={2}>
{showMass && (
<IconButton onClick={switchMassVisible}>
<Switch checked={massVisible} />
<Switch id={'se-wpt-mass-visibility-switch'} checked={massVisible} />
</IconButton>
)}
</Grid>
Expand Down
6 changes: 3 additions & 3 deletions map/src/manager/FavoritesManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -257,9 +257,9 @@ function createGroup(file) {
function addHidden({ pointsGroups, groupName, favArr, mapId, menuId }) {
groupName = normalizeFavoritePointsGroupName(groupName);
let hidden = false;
if (pointsGroups && pointsGroups[groupName]) {
if (pointsGroups[groupName].ext.hidden !== undefined) {
hidden = pointsGroups[groupName].ext.hidden;
if (pointsGroups?.[groupName]) {
if (pointsGroups[groupName].hidden !== undefined) {
hidden = pointsGroups[groupName].hidden;
} else {
hidden = isHidden(pointsGroups, groupName);
}
Expand Down
14 changes: 11 additions & 3 deletions map/src/manager/track/SaveTrackManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import TracksManager, {
GPX_FILE_EXT,
KMZ_FILE_EXT,
} from './TracksManager';
import { syncCloudTrackInfo } from './TrackAppearanceManager';
import cloneDeep from 'lodash-es/cloneDeep';
import isEmpty from 'lodash-es/isEmpty';
import {
Expand All @@ -27,12 +28,16 @@ import {
import Utils from '../../util/Utils';
import { updateSortList } from '../../menu/actions/SortActions';
import { deleteLocalTrack, saveTrackToLocalStorage } from '../../context/LocalTrackStorage';
import { SMART_TYPE } from '../../menu/share/shareConstants';

export function saveTrackToLocal({ ctx, track, selected = true, overwrite = false, cloudAutoSave = false } = {}) {
const newLocalTracks = [...ctx.localTracks];

const originalName = track.name + GPX_FILE_EXT;
if (!track?.name) {
ctx.setRoutingErrorMsg('⚠️ Cannot save nameless local track.');
return;
}

const originalName = removeFileExtension(track.name) + GPX_FILE_EXT;
let localName = TracksManager.prepareName(originalName, true);

// find free name
Expand Down Expand Up @@ -132,7 +137,10 @@ export async function saveTrackToCloud({
// close possibly loaded Cloud track (clean up layers)
ctx.mutateGpxFiles((o) => o[params.name] && (o[params.name].url = null));
const res = await apiPost(`${process.env.REACT_APP_USER_API_SITE}/mapapi/upload-file`, data, { params });
if (res && res?.data?.status === 'ok') {
if (res?.data?.status === 'ok') {
if (type !== FavoritesManager.FAVORITE_FILE_TYPE) {
await syncCloudTrackInfo(ctx, params.name);
}
// re-download gpx
const downloadFile = { ...currentFile, ...params };
if (open) {
Expand Down
Loading