From 3551e80659fc739b0c4a5e837e293b71dd9c2074 Mon Sep 17 00:00:00 2001 From: Dima-1 Date: Thu, 19 Feb 2026 18:37:59 +0200 Subject: [PATCH 01/12] smart folders simple UI --- .../assets/icons/ic_action_folder_smart.svg | 4 ++++ map/src/context/AppContext.js | 23 +++++++++++++++++++ map/src/menu/components/SmartFolder.jsx | 16 ++++++++++--- map/src/menu/share/shareConstants.js | 1 + map/src/menu/tracks/TracksMenu.jsx | 17 ++++++++++++-- 5 files changed, 56 insertions(+), 5 deletions(-) create mode 100644 map/src/assets/icons/ic_action_folder_smart.svg diff --git a/map/src/assets/icons/ic_action_folder_smart.svg b/map/src/assets/icons/ic_action_folder_smart.svg new file mode 100644 index 0000000000..2cd56a55f9 --- /dev/null +++ b/map/src/assets/icons/ic_action_folder_smart.svg @@ -0,0 +1,4 @@ + + + + diff --git a/map/src/context/AppContext.js b/map/src/context/AppContext.js index bb684acb71..0aa24424f3 100644 --- a/map/src/context/AppContext.js +++ b/map/src/context/AppContext.js @@ -95,6 +95,7 @@ async function loadListFiles( setProcessingGroups, setVisibleTracks, setShareWithMeFiles, + setSmartFolders, setUpdateFiles ) { if (loginUser !== listFiles.loginUser) { @@ -113,6 +114,7 @@ async function loadListFiles( }); getFilesForUpdateDetails(res.uniqueFiles, setUpdateFiles); setListFiles(res); + const smartFolders = await loadSmartFolders(setSmartFolders); const favFiles = await loadShareFiles(setShareWithMeFiles); const ownFavorites = TracksManager.getFavoriteGroups(res); const allFavorites = [...ownFavorites, ...favFiles]; @@ -127,6 +129,23 @@ async function loadListFiles( } } +export async function loadSmartFolders(setSmartFolders) { + const res = await getSmartFolders(); + const preparedTracks = Object.fromEntries((res ?? []).map((t) => [t.name, { ...t.files, smartFolder: true }])); + setSmartFolders((prev) => ({ + ...prev, + tracks: res, + })); +} + +export async function getSmartFolders() { + const res = await apiGet(`${process.env.REACT_APP_USER_API_SITE}/mapapi/create-smartfolders`, {}); + if (res.ok) { + return await res.json(); + } + return null; +} + export function getFilesForUpdateDetails(files, setUpdateFiles) { const filesToUpdate = files .filter((f) => f.details && f.details.update && f.type === GPX && f.name.toLowerCase().endsWith(GPX_FILE_EXT)) @@ -362,6 +381,7 @@ export const AppContextProvider = (props) => { const [shareFileMarkers, setShareFileMarkers] = useState(null); const [shareFilesCache, setShareFilesCache] = useState({}); const [shareWithMeFiles, setShareWithMeFiles] = useState(null); + const [smartFolders, setSmartFolders] = useState(null); const [fitBoundsShareTracks, setFitBoundsShareTracks] = useState(null); // selected track const [selectedGpxFile, setSelectedGpxFile] = useState({}); @@ -676,6 +696,7 @@ export const AppContextProvider = (props) => { setProcessingGroups, setVisibleTracks, setShareWithMeFiles, + setSmartFolders, setUpdateFiles ).then(() => { setGpxLoading(false); @@ -874,6 +895,8 @@ export const AppContextProvider = (props) => { setShareFilesCache, shareWithMeFiles, setShareWithMeFiles, + smartFolders, + setSmartFolders, fitBoundsShareTracks, setFitBoundsShareTracks, trackAnalyzer, diff --git a/map/src/menu/components/SmartFolder.jsx b/map/src/menu/components/SmartFolder.jsx index 3e7b557768..5ee74be150 100644 --- a/map/src/menu/components/SmartFolder.jsx +++ b/map/src/menu/components/SmartFolder.jsx @@ -4,13 +4,14 @@ import MenuItemWithLines from './MenuItemWithLines'; import ActionsMenu from '../actions/ActionsMenu'; import React, { useContext, useRef, useState } from 'react'; import { ReactComponent as ShareIcon } from '../../assets/icons/ic_action_folder_share.svg'; +import { ReactComponent as SmartIcon } from '../../assets/icons/ic_action_folder_smart.svg'; import { ReactComponent as MenuIcon } from '../../assets/icons/ic_overflow_menu_white.svg'; import { ReactComponent as MenuIconHover } from '../../assets/icons/ic_overflow_menu_with_background.svg'; import { useTranslation } from 'react-i18next'; import AppContext from '../../context/AppContext'; import SmartFolderActions from '../actions/SmartFolderActions'; import DividerWithMargin from '../../frame/components/dividers/DividerWithMargin'; -import { SHARE_TYPE } from '../share/shareConstants'; +import { SHARE_TYPE, SMART_TYPE } from '../share/shareConstants'; const types = { [SHARE_TYPE]: { @@ -25,9 +26,18 @@ const types = { }, }, }, + [SMART_TYPE]: { + name: 'web:smart', + icon: , + subtypes: { + track: { + substring: 'shared_string_tracks', + }, + }, + }, }; -export default function SmartFolder({ type, subtype, files, onOpenFolder = null }) { +export default function SmartFolder({ type, subtype, files, onOpenFolder = null, name }) { const ctx = useContext(AppContext); const { t } = useTranslation(); @@ -61,7 +71,7 @@ export default function SmartFolder({ type, subtype, files, onOpenFolder = null > {folder.icon} - + {`${Object.entries(files).length} ${t(folderType.substring).toLowerCase()}`} diff --git a/map/src/menu/share/shareConstants.js b/map/src/menu/share/shareConstants.js index 7d03135302..1ae2551313 100644 --- a/map/src/menu/share/shareConstants.js +++ b/map/src/menu/share/shareConstants.js @@ -1,4 +1,5 @@ export const SHARE_TYPE = 'share'; +export const SMART_TYPE = 'smart'; export const SHARE_FILE_TYPE = 'shared_with_me'; export const REQUEST_ACCESS_TYPE = 'request'; export const APPROVED_ACCESS_TYPE = 'approved'; diff --git a/map/src/menu/tracks/TracksMenu.jsx b/map/src/menu/tracks/TracksMenu.jsx index 151212ad82..649fc65a88 100644 --- a/map/src/menu/tracks/TracksMenu.jsx +++ b/map/src/menu/tracks/TracksMenu.jsx @@ -17,7 +17,7 @@ import VisibleTracks, { getCountVisibleTracks } from '../visibletracks/VisibleTr import { useTranslation } from 'react-i18next'; import SmartFolder from '../components/SmartFolder'; import LoginContext from '../../context/LoginContext'; -import { SHARE_TYPE } from '../share/shareConstants'; +import { SHARE_TYPE, SMART_TYPE } from '../share/shareConstants'; import TrackGroupFolder from './TrackGroupFolder'; import { MAIN_URL_WITH_SLASH, MENU_IDS, VISIBLE_TRACKS_URL } from '../../manager/GlobalManager'; import { useLocation, useNavigate } from 'react-router-dom'; @@ -106,6 +106,8 @@ export default function TracksMenu() { const lastGroup = ctx.openGroups[ctx.openGroups.length - 1]; if (lastGroup?.type === SHARE_TYPE) { return ; + } else if (lastGroup?.type === SMART_TYPE) { + return ; } return ; } @@ -158,8 +160,19 @@ export default function TracksMenu() { {!isEmpty(ctx.shareWithMeFiles?.tracks) && ( - + )} + {!isEmpty(ctx.smartFolders?.tracks) && + ctx.smartFolders?.tracks.map((track, index) => { + return ( + + ); + })} {ctx.tracksGroups && (sortGroups && sortGroups.length > 0 ? sortGroups : ctx.tracksGroups) .filter((g) => g.name !== DEFAULT_GROUP_NAME) From b16d5c6004465e60194dba1395a7a4f3620dbd7d Mon Sep 17 00:00:00 2001 From: Dima-1 Date: Sat, 21 Feb 2026 13:15:53 +0200 Subject: [PATCH 02/12] select track --- map/src/context/AppContext.js | 37 ++++++++++++++++++------- map/src/manager/track/TracksManager.js | 12 ++++++-- map/src/menu/components/SmartFolder.jsx | 2 +- map/src/menu/tracks/TracksMenu.jsx | 1 + 4 files changed, 38 insertions(+), 14 deletions(-) diff --git a/map/src/context/AppContext.js b/map/src/context/AppContext.js index 0aa24424f3..3cd9fdfafd 100644 --- a/map/src/context/AppContext.js +++ b/map/src/context/AppContext.js @@ -112,9 +112,8 @@ async function loadListFiles( res.uniqueFiles.forEach((f) => { res.totalUniqueZipSize += f.zipSize; }); - getFilesForUpdateDetails(res.uniqueFiles, setUpdateFiles); setListFiles(res); - const smartFolders = await loadSmartFolders(setSmartFolders); + getFilesForUpdateDetails(res.uniqueFiles, setUpdateFiles, setSmartFolders); const favFiles = await loadShareFiles(setShareWithMeFiles); const ownFavorites = TracksManager.getFavoriteGroups(res); const allFavorites = [...ownFavorites, ...favFiles]; @@ -129,24 +128,38 @@ async function loadListFiles( } } -export async function loadSmartFolders(setSmartFolders) { +export async function loadSmartFolders(setSmartFolders, listFiles) { const res = await getSmartFolders(); - const preparedTracks = Object.fromEntries((res ?? []).map((t) => [t.name, { ...t.files, smartFolder: true }])); + const smartFolders = (res ?? []).map((smartFolder) => { + const files = {}; + (smartFolder.fileIds ?? []).forEach((fileId) => { + const file = listFiles?.find(f => f.id === fileId); + if (file) { + files[file.name] = { ...file, smartFolder: true }; + } + }); + + return { + name: smartFolder.name, + files: files + }; + }); + setSmartFolders((prev) => ({ ...prev, - tracks: res, + tracks: smartFolders, })); } -export async function getSmartFolders() { - const res = await apiGet(`${process.env.REACT_APP_USER_API_SITE}/mapapi/create-smartfolders`, {}); +async function getSmartFolders() { + const res = await apiGet(`${process.env.REACT_APP_USER_API_SITE}/mapapi/create-smart-folders`, {}); if (res.ok) { return await res.json(); } return null; } -export function getFilesForUpdateDetails(files, setUpdateFiles) { +export async function getFilesForUpdateDetails(files, setUpdateFiles, setSmartFolders) { const filesToUpdate = files .filter((f) => f.details && f.details.update && f.type === GPX && f.name.toLowerCase().endsWith(GPX_FILE_EXT)) .map((f) => ({ @@ -157,6 +170,8 @@ export function getFilesForUpdateDetails(files, setUpdateFiles) { })); if (filesToUpdate.length > 0) { setUpdateFiles(filesToUpdate); + } else { + await loadSmartFolders(setSmartFolders, files); } } @@ -677,9 +692,11 @@ export const AppContextProvider = (props) => { } }; - update().then(() => { + (async () => { + await update(); setUpdateFiles(null); - }); + await loadSmartFolders(setSmartFolders, listFiles.uniqueFiles); + })(); }, [updateFiles]); useEffect(() => { diff --git a/map/src/manager/track/TracksManager.js b/map/src/manager/track/TracksManager.js index 626d31aa5b..bc58284779 100644 --- a/map/src/manager/track/TracksManager.js +++ b/map/src/manager/track/TracksManager.js @@ -20,7 +20,7 @@ import anchorme from 'anchorme'; import { isVisibleTrack, updateVisibleCache } from '../../menu/visibletracks/VisibleTracks'; import { getFileStorage, GPX } from '../GlobalManager'; import { closeTrack } from './DeleteTrackManager'; -import { SHARE_TYPE } from '../../menu/share/shareConstants'; +import { SHARE_TYPE, SMART_TYPE } from '../../menu/share/shareConstants'; import { doSort } from '../../menu/actions/SortActions'; import { DEFAULT_SORT_METHOD } from '../../menu/tracks/TracksMenu'; import { TRACKS_KEY } from '../../util/hooks/menu/useRecentDataSaver'; @@ -1573,6 +1573,8 @@ export function updateTracks(ctx, smartf, newTracks) { ...ctx.shareWithMeFiles, tracks: newTracks, }); + } else if (smartf?.type === SMART_TYPE) { + ctx.setGpxFiles(newTracks); } } else { ctx.setGpxFiles(newTracks); @@ -1585,8 +1587,12 @@ function showInfoBlock({ hasUrl, file, ctx, smartf, recentSaver }) { if (smartf?.type === SHARE_TYPE) { allFiles = ctx.shareWithMeFiles.tracks; ctx.setCurrentObjectType(OBJECT_TYPE_SHARE_FILE); - } - if (!smartf) { + } else if (smartf?.type === SMART_TYPE) { + allFiles = ctx.gpxFiles; + if (ctx.currentObjectType !== OBJECT_TRACK_ANALYZER) { + ctx.setCurrentObjectType(OBJECT_TYPE_CLOUD_TRACK); + } + } else if (!smartf) { //default case for cloud tracks allFiles = ctx.gpxFiles; diff --git a/map/src/menu/components/SmartFolder.jsx b/map/src/menu/components/SmartFolder.jsx index 5ee74be150..7c8ab95f86 100644 --- a/map/src/menu/components/SmartFolder.jsx +++ b/map/src/menu/components/SmartFolder.jsx @@ -71,7 +71,7 @@ export default function SmartFolder({ type, subtype, files, onOpenFolder = null, > {folder.icon} - + {`${Object.entries(files).length} ${t(folderType.substring).toLowerCase()}`} diff --git a/map/src/menu/tracks/TracksMenu.jsx b/map/src/menu/tracks/TracksMenu.jsx index 649fc65a88..a8994181c1 100644 --- a/map/src/menu/tracks/TracksMenu.jsx +++ b/map/src/menu/tracks/TracksMenu.jsx @@ -166,6 +166,7 @@ export default function TracksMenu() { ctx.smartFolders?.tracks.map((track, index) => { return ( Date: Mon, 23 Feb 2026 15:48:15 +0200 Subject: [PATCH 03/12] use CloudTrackGroup instead of SmartFolder --- map/src/manager/track/SaveTrackManager.js | 2 +- map/src/menu/components/SmartFolder.jsx | 16 ++-------- map/src/menu/tracks/CloudTrackGroup.jsx | 37 ++++++++++++++++++----- map/src/menu/tracks/TrackGroupFolder.jsx | 3 +- map/src/menu/tracks/TracksMenu.jsx | 15 ++++++--- 5 files changed, 46 insertions(+), 27 deletions(-) diff --git a/map/src/manager/track/SaveTrackManager.js b/map/src/manager/track/SaveTrackManager.js index 9ead3eb842..b49c6e9cfb 100644 --- a/map/src/manager/track/SaveTrackManager.js +++ b/map/src/manager/track/SaveTrackManager.js @@ -337,7 +337,7 @@ export async function refreshGlobalFiles({ const respGetFiles = await apiGet(`${process.env.REACT_APP_USER_API_SITE}/mapapi/list-files`, {}); const resJson = await respGetFiles.json(); if (resJson && resJson.uniqueFiles) { - getFilesForUpdateDetails(resJson.uniqueFiles, ctx.setUpdateFiles); + getFilesForUpdateDetails(resJson.uniqueFiles, ctx.setUpdateFiles,ctx.setSmartFolders); ctx.setListFiles(resJson); } if (type === OBJECT_TYPE_FAVORITE) { diff --git a/map/src/menu/components/SmartFolder.jsx b/map/src/menu/components/SmartFolder.jsx index 7c8ab95f86..3e7b557768 100644 --- a/map/src/menu/components/SmartFolder.jsx +++ b/map/src/menu/components/SmartFolder.jsx @@ -4,14 +4,13 @@ import MenuItemWithLines from './MenuItemWithLines'; import ActionsMenu from '../actions/ActionsMenu'; import React, { useContext, useRef, useState } from 'react'; import { ReactComponent as ShareIcon } from '../../assets/icons/ic_action_folder_share.svg'; -import { ReactComponent as SmartIcon } from '../../assets/icons/ic_action_folder_smart.svg'; import { ReactComponent as MenuIcon } from '../../assets/icons/ic_overflow_menu_white.svg'; import { ReactComponent as MenuIconHover } from '../../assets/icons/ic_overflow_menu_with_background.svg'; import { useTranslation } from 'react-i18next'; import AppContext from '../../context/AppContext'; import SmartFolderActions from '../actions/SmartFolderActions'; import DividerWithMargin from '../../frame/components/dividers/DividerWithMargin'; -import { SHARE_TYPE, SMART_TYPE } from '../share/shareConstants'; +import { SHARE_TYPE } from '../share/shareConstants'; const types = { [SHARE_TYPE]: { @@ -26,18 +25,9 @@ const types = { }, }, }, - [SMART_TYPE]: { - name: 'web:smart', - icon: , - subtypes: { - track: { - substring: 'shared_string_tracks', - }, - }, - }, }; -export default function SmartFolder({ type, subtype, files, onOpenFolder = null, name }) { +export default function SmartFolder({ type, subtype, files, onOpenFolder = null }) { const ctx = useContext(AppContext); const { t } = useTranslation(); @@ -71,7 +61,7 @@ export default function SmartFolder({ type, subtype, files, onOpenFolder = null, > {folder.icon} - + {`${Object.entries(files).length} ${t(folderType.substring).toLowerCase()}`} diff --git a/map/src/menu/tracks/CloudTrackGroup.jsx b/map/src/menu/tracks/CloudTrackGroup.jsx index eb4a440160..afcfe4623f 100644 --- a/map/src/menu/tracks/CloudTrackGroup.jsx +++ b/map/src/menu/tracks/CloudTrackGroup.jsx @@ -2,6 +2,7 @@ import { ListItemIcon, ListItemText, MenuItem, Typography } from '@mui/material' import React, { useContext, useEffect, useRef, useState } from 'react'; import AppContext from '../../context/AppContext'; import { ReactComponent as FolderIcon } from '../../assets/icons/ic_action_folder.svg'; +import { ReactComponent as SmartIcon } from '../../assets/icons/ic_action_folder_smart.svg'; import styles from '../trackfavmenu.module.css'; import GroupActions from '../actions/GroupActions'; import ActionsMenu from '../actions/ActionsMenu'; @@ -10,6 +11,7 @@ import { useTranslation } from 'react-i18next'; import DividerWithMargin from '../../frame/components/dividers/DividerWithMargin'; import ThreeDotsButton from '../../frame/components/btns/ThreeDotsButton'; import { fmt } from '../../util/dateFmt'; +import { SMART_TYPE } from '../share/shareConstants'; export default function CloudTrackGroup({ index, group }) { const ctx = useContext(AppContext); @@ -25,25 +27,46 @@ export default function CloudTrackGroup({ index, group }) { } }, [ctx.openedPopper]); + const getFolderIcon = () => { + if (group.type === SMART_TYPE) { + return ; + } + return ; + }; + + const handleClick = (e) => { + if (e.target !== 'path') { + if (group.type === SMART_TYPE) { + ctx.setOpenGroups((prevState) => [...prevState, { files: group.files, type: group.type, name: group.name }]); + } else { + ctx.setOpenGroups((prevState) => [...prevState, group]); + } + } + }; + + const getInfoText = () => { + if (group.type === SMART_TYPE) { + const fileCount = Array.isArray(group.files) ? group.files.length : Object.values(group.files).length; + return `${fileCount} ${t('shared_string_gpx_files').toLowerCase()}`; + } + return `${fmt.monthShortDay(group.lastModifiedData)}, ${t('shared_string_gpx_files').toLowerCase()} ${group.realSize}`; + }; + return ( <> { - if (e.target !== 'path') { - ctx.setOpenGroups((prevState) => [...prevState, group]); - } - }} + onClick={handleClick} > - + {getFolderIcon()} - {`${fmt.monthShortDay(group.lastModifiedData)}, ${t('shared_string_gpx_files').toLowerCase()} ${group.realSize}`} + {getInfoText()} { if (smartf) { setProcessingGroup(true); - const trackGroups = createTrackGroups({ files: smartf.files, isSmartf: true, ctx }); + const files = Array.isArray(smartf.files) ? smartf.files : Object.values(smartf.files ?? {}); + const trackGroups = createTrackGroups({ files, isSmartf: true, ctx }); if (trackGroups.length > 0) { let found = findGroupByName(trackGroups, DEFAULT_GROUP_NAME); if (found) { diff --git a/map/src/menu/tracks/TracksMenu.jsx b/map/src/menu/tracks/TracksMenu.jsx index a8994181c1..4b90ff3501 100644 --- a/map/src/menu/tracks/TracksMenu.jsx +++ b/map/src/menu/tracks/TracksMenu.jsx @@ -164,13 +164,18 @@ export default function TracksMenu() { )} {!isEmpty(ctx.smartFolders?.tracks) && ctx.smartFolders?.tracks.map((track, index) => { + const filesArray = Array.isArray(track.files) + ? track.files + : Object.values(track.files); return ( - ); })} From c4387567b64baf9eadcd08bb6ed7a8f7a0d0548a Mon Sep 17 00:00:00 2001 From: Dima-1 Date: Mon, 23 Feb 2026 17:11:25 +0200 Subject: [PATCH 04/12] use userFilePaths instead of fileIds --- map/src/context/AppContext.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/map/src/context/AppContext.js b/map/src/context/AppContext.js index 3cd9fdfafd..d94f2637b9 100644 --- a/map/src/context/AppContext.js +++ b/map/src/context/AppContext.js @@ -132,8 +132,8 @@ export async function loadSmartFolders(setSmartFolders, listFiles) { const res = await getSmartFolders(); const smartFolders = (res ?? []).map((smartFolder) => { const files = {}; - (smartFolder.fileIds ?? []).forEach((fileId) => { - const file = listFiles?.find(f => f.id === fileId); + (smartFolder.userFilePaths ?? []).forEach((path) => { + const file = listFiles?.find(f => f.name === path); if (file) { files[file.name] = { ...file, smartFolder: true }; } From 65245912279bb28709ca1120d1793ca93719f94d Mon Sep 17 00:00:00 2001 From: Dima-1 Date: Tue, 24 Feb 2026 09:09:40 +0200 Subject: [PATCH 05/12] fix refresh smart folders after delete file --- map/src/manager/track/DeleteTrackManager.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/map/src/manager/track/DeleteTrackManager.js b/map/src/manager/track/DeleteTrackManager.js index bb516a0144..7871ce9f93 100644 --- a/map/src/manager/track/DeleteTrackManager.js +++ b/map/src/manager/track/DeleteTrackManager.js @@ -1,4 +1,4 @@ -import { isCloudTrack, isLocalTrack, loadShareFiles, OBJECT_TYPE_FAVORITE } from '../../context/AppContext'; +import { isCloudTrack, isLocalTrack, loadShareFiles, OBJECT_TYPE_FAVORITE, loadSmartFolders } from '../../context/AppContext'; import { apiGet, apiPost } from '../../util/HttpApi'; import { findGroupByName, getAllVisibleFiles } from './TracksManager'; import { refreshGlobalFiles } from './SaveTrackManager'; @@ -79,6 +79,7 @@ async function deleteCloudFile(name, type, ctx) { } return { ...o }; }); + await loadSmartFolders(ctx.setSmartFolders, ctx.listFiles.uniqueFiles); } } } From ef1e30434f47a1a5585309ee9c6e9dbabfb0d3ba Mon Sep 17 00:00:00 2001 From: Dima-1 Date: Wed, 25 Feb 2026 13:19:22 +0200 Subject: [PATCH 06/12] Add sorting smart folders --- map/src/context/AppContext.js | 15 ++++++-- map/src/manager/track/SaveTrackManager.js | 2 +- map/src/menu/actions/SortActions.jsx | 15 ++++++-- map/src/menu/tracks/CloudTrackGroup.jsx | 9 ++--- map/src/menu/tracks/TracksMenu.jsx | 43 ++++++++++++----------- 5 files changed, 54 insertions(+), 30 deletions(-) diff --git a/map/src/context/AppContext.js b/map/src/context/AppContext.js index d94f2637b9..06f566723b 100644 --- a/map/src/context/AppContext.js +++ b/map/src/context/AppContext.js @@ -132,16 +132,25 @@ export async function loadSmartFolders(setSmartFolders, listFiles) { const res = await getSmartFolders(); const smartFolders = (res ?? []).map((smartFolder) => { const files = {}; + let minMs = Infinity; + let minData = null; + (smartFolder.userFilePaths ?? []).forEach((path) => { - const file = listFiles?.find(f => f.name === path); + const file = listFiles?.find((f) => f.name === path); if (file) { files[file.name] = { ...file, smartFolder: true }; + if (file.updatetimems < minMs) { + minMs = file.updatetimems; + minData = file.updatetime; + } } }); - + return { name: smartFolder.name, - files: files + files: files, + lastModifiedMs: minMs !== Infinity ? minMs : null, + lastModifiedData: minData, }; }); diff --git a/map/src/manager/track/SaveTrackManager.js b/map/src/manager/track/SaveTrackManager.js index b49c6e9cfb..872923f53a 100644 --- a/map/src/manager/track/SaveTrackManager.js +++ b/map/src/manager/track/SaveTrackManager.js @@ -337,7 +337,7 @@ export async function refreshGlobalFiles({ const respGetFiles = await apiGet(`${process.env.REACT_APP_USER_API_SITE}/mapapi/list-files`, {}); const resJson = await respGetFiles.json(); if (resJson && resJson.uniqueFiles) { - getFilesForUpdateDetails(resJson.uniqueFiles, ctx.setUpdateFiles,ctx.setSmartFolders); + getFilesForUpdateDetails(resJson.uniqueFiles, ctx.setUpdateFiles, ctx.setSmartFolders); ctx.setListFiles(resJson); } if (type === OBJECT_TYPE_FAVORITE) { diff --git a/map/src/menu/actions/SortActions.jsx b/map/src/menu/actions/SortActions.jsx index be13994318..ac120694c7 100644 --- a/map/src/menu/actions/SortActions.jsx +++ b/map/src/menu/actions/SortActions.jsx @@ -14,10 +14,11 @@ import { ReactComponent as ShortDurationIcon } from '../../assets/icons/ic_actio import styles from '../trackfavmenu.module.css'; import AppContext from '../../context/AppContext'; import FavoritesManager, { DEFAULT_FAV_GROUP_NAME } from '../../manager/FavoritesManager'; +import { DEFAULT_GROUP_NAME } from '../../manager/track/TracksManager'; import i18n from '../../i18n'; import ActionItem from '../components/ActionItem'; import { getSelectedSort } from '../components/buttons/SortFilesButton'; -import { SHARE_TYPE } from '../share/shareConstants'; +import { SHARE_TYPE, SMART_TYPE } from '../share/shareConstants'; const az = (a, b) => (a > b) - (a < b); @@ -254,6 +255,16 @@ const SortActions = forwardRef( const groups = () => { if (trackGroup) { + if (trackGroup.name === DEFAULT_GROUP_NAME || trackGroup.fullName === DEFAULT_GROUP_NAME) { + return [ + ...(trackGroup.subfolders || []), + ...(ctx.smartFolders?.tracks || []).map((sf) => ({ + ...sf, + type: SMART_TYPE, + files: Array.isArray(sf.files) ? sf.files : Object.values(sf.files), + })), + ]; + } return trackGroup.subfolders; } else if (favoriteGroup) { if (smartf?.type === SHARE_TYPE) { @@ -281,7 +292,7 @@ const SortActions = forwardRef( favoriteGroup, }); } - }, [files(), currentMethod]); + }, [files(), currentMethod, ctx.smartFolders?.tracks]); const handleChange = (event) => { const method = event.target.value; diff --git a/map/src/menu/tracks/CloudTrackGroup.jsx b/map/src/menu/tracks/CloudTrackGroup.jsx index afcfe4623f..4a8a240f83 100644 --- a/map/src/menu/tracks/CloudTrackGroup.jsx +++ b/map/src/menu/tracks/CloudTrackGroup.jsx @@ -37,7 +37,10 @@ export default function CloudTrackGroup({ index, group }) { const handleClick = (e) => { if (e.target !== 'path') { if (group.type === SMART_TYPE) { - ctx.setOpenGroups((prevState) => [...prevState, { files: group.files, type: group.type, name: group.name }]); + ctx.setOpenGroups((prevState) => [ + ...prevState, + { files: group.files, type: group.type, name: group.name }, + ]); } else { ctx.setOpenGroups((prevState) => [...prevState, group]); } @@ -60,9 +63,7 @@ export default function CloudTrackGroup({ index, group }) { id={'se-menu-cloud-' + group.name} onClick={handleClick} > - - {getFolderIcon()} - + {getFolderIcon()} diff --git a/map/src/menu/tracks/TracksMenu.jsx b/map/src/menu/tracks/TracksMenu.jsx index 4b90ff3501..55396bb234 100644 --- a/map/src/menu/tracks/TracksMenu.jsx +++ b/map/src/menu/tracks/TracksMenu.jsx @@ -65,19 +65,29 @@ export default function TracksMenu() { } else { setDefaultGroup(defaultGroupWithFolders); } + + const allGroups = [ + ...(defaultGroupWithFolders.subfolders || []), + ...(ctx.smartFolders?.tracks || []).map((sf) => ({ + ...sf, + type: SMART_TYPE, + files: Array.isArray(sf.files) ? sf.files : Object.values(sf.files), + })), + ]; + doSort({ method: ctx.selectedSort?.tracks?.[DEFAULT_GROUP_NAME] ?? DEFAULT_SORT_METHOD, setSortFiles, setSortGroups, files: defGroup ? defGroup.groupFiles : [], - groups: defaultGroupWithFolders.subfolders, + groups: allGroups, }); } else { setDefaultGroup(defaultGroupWithFolders); setSortFiles([]); setSortGroups([]); } - }, [ctx.tracksGroups]); + }, [ctx.tracksGroups, ctx.smartFolders?.tracks, ctx.selectedSort?.tracks?.[DEFAULT_GROUP_NAME]]); const defaultGroupItems = useMemo(() => { if (defaultGroup?.groupFiles) { @@ -162,25 +172,18 @@ export default function TracksMenu() { {!isEmpty(ctx.shareWithMeFiles?.tracks) && ( )} - {!isEmpty(ctx.smartFolders?.tracks) && - ctx.smartFolders?.tracks.map((track, index) => { - const filesArray = Array.isArray(track.files) - ? track.files - : Object.values(track.files); - return ( - - ); - })} {ctx.tracksGroups && - (sortGroups && sortGroups.length > 0 ? sortGroups : ctx.tracksGroups) + (sortGroups && sortGroups.length > 0 + ? sortGroups + : [ + ...(ctx.tracksGroups || []), + ...(ctx.smartFolders?.tracks || []).map((sf) => ({ + ...sf, + type: SMART_TYPE, + files: Array.isArray(sf.files) ? sf.files : Object.values(sf.files), + })), + ] + ) .filter((g) => g.name !== DEFAULT_GROUP_NAME) .map((group, index) => { return ; From 7035c73b2392c5709ff9832a20334b76a4eeab2f Mon Sep 17 00:00:00 2001 From: Dima-1 Date: Thu, 26 Feb 2026 17:53:58 +0200 Subject: [PATCH 07/12] Merge groups with smart folders --- map/src/context/AppContext.js | 39 +++++++++++---------- map/src/frame/GlobalFrame.js | 7 ++-- map/src/manager/track/DeleteTrackManager.js | 10 ++++-- map/src/manager/track/SaveTrackManager.js | 9 +++-- map/src/manager/track/TracksManager.js | 12 ++----- map/src/menu/actions/SortActions.jsx | 14 +++----- map/src/menu/tracks/CloudTrackGroup.jsx | 12 ++----- map/src/menu/tracks/TrackGroupFolder.jsx | 3 +- map/src/menu/tracks/TracksMenu.jsx | 25 ++----------- 9 files changed, 53 insertions(+), 78 deletions(-) diff --git a/map/src/context/AppContext.js b/map/src/context/AppContext.js index 06f566723b..c5c01d2db1 100644 --- a/map/src/context/AppContext.js +++ b/map/src/context/AppContext.js @@ -32,6 +32,7 @@ import { SEARCH_RESULTS_KEY, TRACKS_KEY, } from '../util/hooks/menu/useRecentDataSaver'; +import { SMART_TYPE } from '../menu/share/shareConstants'; export const OBJECT_TYPE_LOCAL_TRACK = 'local_track'; // track in localStorage export const OBJECT_TYPE_CLOUD_TRACK = 'cloud_track'; // track in OsmAnd Cloud @@ -95,7 +96,7 @@ async function loadListFiles( setProcessingGroups, setVisibleTracks, setShareWithMeFiles, - setSmartFolders, + setTracksGroups, setUpdateFiles ) { if (loginUser !== listFiles.loginUser) { @@ -113,7 +114,7 @@ async function loadListFiles( res.totalUniqueZipSize += f.zipSize; }); setListFiles(res); - getFilesForUpdateDetails(res.uniqueFiles, setUpdateFiles, setSmartFolders); + getFilesForUpdateDetails(res.uniqueFiles, setUpdateFiles, setTracksGroups); const favFiles = await loadShareFiles(setShareWithMeFiles); const ownFavorites = TracksManager.getFavoriteGroups(res); const allFavorites = [...ownFavorites, ...favFiles]; @@ -128,17 +129,17 @@ async function loadListFiles( } } -export async function loadSmartFolders(setSmartFolders, listFiles) { +export async function loadSmartFolders(setTracksGroups, listFiles) { const res = await getSmartFolders(); - const smartFolders = (res ?? []).map((smartFolder) => { - const files = {}; + const smartFolderGroups = (res ?? []).map((smartFolder) => { + const filesArray = []; let minMs = Infinity; let minData = null; (smartFolder.userFilePaths ?? []).forEach((path) => { const file = listFiles?.find((f) => f.name === path); if (file) { - files[file.name] = { ...file, smartFolder: true }; + filesArray.push({ ...file, smartFolder: true }); if (file.updatetimems < minMs) { minMs = file.updatetimems; minData = file.updatetime; @@ -148,16 +149,21 @@ export async function loadSmartFolders(setSmartFolders, listFiles) { return { name: smartFolder.name, - files: files, + fullName: smartFolder.name, + type: SMART_TYPE, + subfolders: [], + groupFiles: filesArray, + files: filesArray, + realSize: filesArray.length, lastModifiedMs: minMs !== Infinity ? minMs : null, lastModifiedData: minData, }; }); - setSmartFolders((prev) => ({ - ...prev, - tracks: smartFolders, - })); + setTracksGroups((prev) => { + const withoutSmartFolders = prev.filter((g) => g.type !== SMART_TYPE); + return [...withoutSmartFolders, ...smartFolderGroups]; + }); } async function getSmartFolders() { @@ -168,7 +174,7 @@ async function getSmartFolders() { return null; } -export async function getFilesForUpdateDetails(files, setUpdateFiles, setSmartFolders) { +export async function getFilesForUpdateDetails(files, setUpdateFiles, setTracksGroups) { const filesToUpdate = files .filter((f) => f.details && f.details.update && f.type === GPX && f.name.toLowerCase().endsWith(GPX_FILE_EXT)) .map((f) => ({ @@ -180,7 +186,7 @@ export async function getFilesForUpdateDetails(files, setUpdateFiles, setSmartFo if (filesToUpdate.length > 0) { setUpdateFiles(filesToUpdate); } else { - await loadSmartFolders(setSmartFolders, files); + await loadSmartFolders(setTracksGroups, files); } } @@ -405,7 +411,6 @@ export const AppContextProvider = (props) => { const [shareFileMarkers, setShareFileMarkers] = useState(null); const [shareFilesCache, setShareFilesCache] = useState({}); const [shareWithMeFiles, setShareWithMeFiles] = useState(null); - const [smartFolders, setSmartFolders] = useState(null); const [fitBoundsShareTracks, setFitBoundsShareTracks] = useState(null); // selected track const [selectedGpxFile, setSelectedGpxFile] = useState({}); @@ -704,7 +709,7 @@ export const AppContextProvider = (props) => { (async () => { await update(); setUpdateFiles(null); - await loadSmartFolders(setSmartFolders, listFiles.uniqueFiles); + await loadSmartFolders(setTracksGroups, listFiles.uniqueFiles); })(); }, [updateFiles]); @@ -722,7 +727,7 @@ export const AppContextProvider = (props) => { setProcessingGroups, setVisibleTracks, setShareWithMeFiles, - setSmartFolders, + setTracksGroups, setUpdateFiles ).then(() => { setGpxLoading(false); @@ -921,8 +926,6 @@ export const AppContextProvider = (props) => { setShareFilesCache, shareWithMeFiles, setShareWithMeFiles, - smartFolders, - setSmartFolders, fitBoundsShareTracks, setFitBoundsShareTracks, trackAnalyzer, diff --git a/map/src/frame/GlobalFrame.js b/map/src/frame/GlobalFrame.js index 939a385387..0dba994235 100644 --- a/map/src/frame/GlobalFrame.js +++ b/map/src/frame/GlobalFrame.js @@ -35,6 +35,7 @@ import GlobalGraph from '../graph/mapGraph/GlobalGraph'; import LoginContext from '../context/LoginContext'; import { poiUrlParams } from '../manager/PoiManager'; import { createUrlParams } from '../util/Utils'; +import { SMART_TYPE } from '../menu/share/shareConstants'; const ENCODED_COMMA = '%2C'; const ENCODED_COLON = '%3A'; @@ -365,9 +366,11 @@ const GlobalFrame = () => { if (!isEmpty(ctx.listFiles)) { const files = getGpxFiles(ctx.listFiles); const trackGroups = createTrackGroups({ files, ctx }); - ctx.setTracksGroups(trackGroups); + const smartFolders = ctx.tracksGroups?.filter((g) => g.type === SMART_TYPE) || []; + ctx.setTracksGroups([...trackGroups, ...smartFolders]); } else { - ctx.setTracksGroups([]); + const smartFolders = ctx.tracksGroups?.filter((g) => g.type === SMART_TYPE) || []; + ctx.setTracksGroups(smartFolders); } }, [ctx.listFiles, ctx.selectedSort]); diff --git a/map/src/manager/track/DeleteTrackManager.js b/map/src/manager/track/DeleteTrackManager.js index 7871ce9f93..894d5874ab 100644 --- a/map/src/manager/track/DeleteTrackManager.js +++ b/map/src/manager/track/DeleteTrackManager.js @@ -1,4 +1,10 @@ -import { isCloudTrack, isLocalTrack, loadShareFiles, OBJECT_TYPE_FAVORITE, loadSmartFolders } from '../../context/AppContext'; +import { + isCloudTrack, + isLocalTrack, + loadShareFiles, + OBJECT_TYPE_FAVORITE, + loadSmartFolders, +} from '../../context/AppContext'; import { apiGet, apiPost } from '../../util/HttpApi'; import { findGroupByName, getAllVisibleFiles } from './TracksManager'; import { refreshGlobalFiles } from './SaveTrackManager'; @@ -79,7 +85,7 @@ async function deleteCloudFile(name, type, ctx) { } return { ...o }; }); - await loadSmartFolders(ctx.setSmartFolders, ctx.listFiles.uniqueFiles); + await loadSmartFolders(ctx.setTracksGroups, ctx.listFiles.uniqueFiles); } } } diff --git a/map/src/manager/track/SaveTrackManager.js b/map/src/manager/track/SaveTrackManager.js index 872923f53a..fd0dd1773d 100644 --- a/map/src/manager/track/SaveTrackManager.js +++ b/map/src/manager/track/SaveTrackManager.js @@ -26,6 +26,7 @@ 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]; @@ -337,7 +338,7 @@ export async function refreshGlobalFiles({ const respGetFiles = await apiGet(`${process.env.REACT_APP_USER_API_SITE}/mapapi/list-files`, {}); const resJson = await respGetFiles.json(); if (resJson && resJson.uniqueFiles) { - getFilesForUpdateDetails(resJson.uniqueFiles, ctx.setUpdateFiles, ctx.setSmartFolders); + getFilesForUpdateDetails(resJson.uniqueFiles, ctx.setUpdateFiles, ctx.setTracksGroups); ctx.setListFiles(resJson); } if (type === OBJECT_TYPE_FAVORITE) { @@ -404,9 +405,11 @@ function updateTrackGroups(listFiles, ctx) { if (!isEmpty(listFiles)) { const files = getGpxFiles(listFiles); const trackGroups = createTrackGroups({ files, ctx }); - ctx.setTracksGroups(trackGroups); + const smartFolders = ctx.tracksGroups?.filter((g) => g.type === SMART_TYPE) || []; + ctx.setTracksGroups([...trackGroups, ...smartFolders]); } else { - ctx.setTracksGroups([]); + const smartFolders = ctx.tracksGroups?.filter((g) => g.type === SMART_TYPE) || []; + ctx.setTracksGroups(smartFolders); } } diff --git a/map/src/manager/track/TracksManager.js b/map/src/manager/track/TracksManager.js index bc58284779..626d31aa5b 100644 --- a/map/src/manager/track/TracksManager.js +++ b/map/src/manager/track/TracksManager.js @@ -20,7 +20,7 @@ import anchorme from 'anchorme'; import { isVisibleTrack, updateVisibleCache } from '../../menu/visibletracks/VisibleTracks'; import { getFileStorage, GPX } from '../GlobalManager'; import { closeTrack } from './DeleteTrackManager'; -import { SHARE_TYPE, SMART_TYPE } from '../../menu/share/shareConstants'; +import { SHARE_TYPE } from '../../menu/share/shareConstants'; import { doSort } from '../../menu/actions/SortActions'; import { DEFAULT_SORT_METHOD } from '../../menu/tracks/TracksMenu'; import { TRACKS_KEY } from '../../util/hooks/menu/useRecentDataSaver'; @@ -1573,8 +1573,6 @@ export function updateTracks(ctx, smartf, newTracks) { ...ctx.shareWithMeFiles, tracks: newTracks, }); - } else if (smartf?.type === SMART_TYPE) { - ctx.setGpxFiles(newTracks); } } else { ctx.setGpxFiles(newTracks); @@ -1587,12 +1585,8 @@ function showInfoBlock({ hasUrl, file, ctx, smartf, recentSaver }) { if (smartf?.type === SHARE_TYPE) { allFiles = ctx.shareWithMeFiles.tracks; ctx.setCurrentObjectType(OBJECT_TYPE_SHARE_FILE); - } else if (smartf?.type === SMART_TYPE) { - allFiles = ctx.gpxFiles; - if (ctx.currentObjectType !== OBJECT_TRACK_ANALYZER) { - ctx.setCurrentObjectType(OBJECT_TYPE_CLOUD_TRACK); - } - } else if (!smartf) { + } + if (!smartf) { //default case for cloud tracks allFiles = ctx.gpxFiles; diff --git a/map/src/menu/actions/SortActions.jsx b/map/src/menu/actions/SortActions.jsx index ac120694c7..e3f1d34598 100644 --- a/map/src/menu/actions/SortActions.jsx +++ b/map/src/menu/actions/SortActions.jsx @@ -14,11 +14,11 @@ import { ReactComponent as ShortDurationIcon } from '../../assets/icons/ic_actio import styles from '../trackfavmenu.module.css'; import AppContext from '../../context/AppContext'; import FavoritesManager, { DEFAULT_FAV_GROUP_NAME } from '../../manager/FavoritesManager'; -import { DEFAULT_GROUP_NAME } from '../../manager/track/TracksManager'; import i18n from '../../i18n'; import ActionItem from '../components/ActionItem'; import { getSelectedSort } from '../components/buttons/SortFilesButton'; import { SHARE_TYPE, SMART_TYPE } from '../share/shareConstants'; +import { DEFAULT_GROUP_NAME } from '../../manager/track/TracksManager'; const az = (a, b) => (a > b) - (a < b); @@ -256,14 +256,8 @@ const SortActions = forwardRef( const groups = () => { if (trackGroup) { if (trackGroup.name === DEFAULT_GROUP_NAME || trackGroup.fullName === DEFAULT_GROUP_NAME) { - return [ - ...(trackGroup.subfolders || []), - ...(ctx.smartFolders?.tracks || []).map((sf) => ({ - ...sf, - type: SMART_TYPE, - files: Array.isArray(sf.files) ? sf.files : Object.values(sf.files), - })), - ]; + const smartFolders = ctx.tracksGroups?.filter((g) => g.type === SMART_TYPE) || []; + return [...(trackGroup.subfolders || []), ...smartFolders]; } return trackGroup.subfolders; } else if (favoriteGroup) { @@ -292,7 +286,7 @@ const SortActions = forwardRef( favoriteGroup, }); } - }, [files(), currentMethod, ctx.smartFolders?.tracks]); + }, [files(), currentMethod]); const handleChange = (event) => { const method = event.target.value; diff --git a/map/src/menu/tracks/CloudTrackGroup.jsx b/map/src/menu/tracks/CloudTrackGroup.jsx index 4a8a240f83..054878228c 100644 --- a/map/src/menu/tracks/CloudTrackGroup.jsx +++ b/map/src/menu/tracks/CloudTrackGroup.jsx @@ -36,21 +36,13 @@ export default function CloudTrackGroup({ index, group }) { const handleClick = (e) => { if (e.target !== 'path') { - if (group.type === SMART_TYPE) { - ctx.setOpenGroups((prevState) => [ - ...prevState, - { files: group.files, type: group.type, name: group.name }, - ]); - } else { - ctx.setOpenGroups((prevState) => [...prevState, group]); - } + ctx.setOpenGroups((prevState) => [...prevState, group]); } }; const getInfoText = () => { if (group.type === SMART_TYPE) { - const fileCount = Array.isArray(group.files) ? group.files.length : Object.values(group.files).length; - return `${fileCount} ${t('shared_string_gpx_files').toLowerCase()}`; + return `${group.realSize} ${t('shared_string_gpx_files').toLowerCase()}`; } return `${fmt.monthShortDay(group.lastModifiedData)}, ${t('shared_string_gpx_files').toLowerCase()} ${group.realSize}`; }; diff --git a/map/src/menu/tracks/TrackGroupFolder.jsx b/map/src/menu/tracks/TrackGroupFolder.jsx index 6547011de1..a848790256 100644 --- a/map/src/menu/tracks/TrackGroupFolder.jsx +++ b/map/src/menu/tracks/TrackGroupFolder.jsx @@ -44,8 +44,7 @@ export default function TrackGroupFolder({ folder = null, smartf = null }) { useEffect(() => { if (smartf) { setProcessingGroup(true); - const files = Array.isArray(smartf.files) ? smartf.files : Object.values(smartf.files ?? {}); - const trackGroups = createTrackGroups({ files, isSmartf: true, ctx }); + const trackGroups = createTrackGroups({ files: smartf.files, isSmartf: true, ctx }); if (trackGroups.length > 0) { let found = findGroupByName(trackGroups, DEFAULT_GROUP_NAME); if (found) { diff --git a/map/src/menu/tracks/TracksMenu.jsx b/map/src/menu/tracks/TracksMenu.jsx index 55396bb234..37036f3d7c 100644 --- a/map/src/menu/tracks/TracksMenu.jsx +++ b/map/src/menu/tracks/TracksMenu.jsx @@ -66,14 +66,7 @@ export default function TracksMenu() { setDefaultGroup(defaultGroupWithFolders); } - const allGroups = [ - ...(defaultGroupWithFolders.subfolders || []), - ...(ctx.smartFolders?.tracks || []).map((sf) => ({ - ...sf, - type: SMART_TYPE, - files: Array.isArray(sf.files) ? sf.files : Object.values(sf.files), - })), - ]; + const allGroups = defaultGroupWithFolders.subfolders || []; doSort({ method: ctx.selectedSort?.tracks?.[DEFAULT_GROUP_NAME] ?? DEFAULT_SORT_METHOD, @@ -87,7 +80,7 @@ export default function TracksMenu() { setSortFiles([]); setSortGroups([]); } - }, [ctx.tracksGroups, ctx.smartFolders?.tracks, ctx.selectedSort?.tracks?.[DEFAULT_GROUP_NAME]]); + }, [ctx.tracksGroups]); const defaultGroupItems = useMemo(() => { if (defaultGroup?.groupFiles) { @@ -116,8 +109,6 @@ export default function TracksMenu() { const lastGroup = ctx.openGroups[ctx.openGroups.length - 1]; if (lastGroup?.type === SHARE_TYPE) { return ; - } else if (lastGroup?.type === SMART_TYPE) { - return ; } return ; } @@ -173,17 +164,7 @@ export default function TracksMenu() { )} {ctx.tracksGroups && - (sortGroups && sortGroups.length > 0 - ? sortGroups - : [ - ...(ctx.tracksGroups || []), - ...(ctx.smartFolders?.tracks || []).map((sf) => ({ - ...sf, - type: SMART_TYPE, - files: Array.isArray(sf.files) ? sf.files : Object.values(sf.files), - })), - ] - ) + (sortGroups?.length > 0 ? sortGroups : ctx.tracksGroups) .filter((g) => g.name !== DEFAULT_GROUP_NAME) .map((group, index) => { return ; From bd28fe1b6c8ed486d924f1b8252b7f311cb957c0 Mon Sep 17 00:00:00 2001 From: Dima-1 Date: Mon, 2 Mar 2026 18:39:33 +0200 Subject: [PATCH 08/12] Fix review --- map/src/context/AppContext.js | 6 +++--- map/src/frame/GlobalFrame.js | 3 +-- map/src/manager/track/SaveTrackManager.js | 3 +-- map/src/manager/track/TracksManager.js | 5 ++++- map/src/menu/tracks/TracksMenu.jsx | 5 +---- 5 files changed, 10 insertions(+), 12 deletions(-) diff --git a/map/src/context/AppContext.js b/map/src/context/AppContext.js index c5c01d2db1..aa8fcea9aa 100644 --- a/map/src/context/AppContext.js +++ b/map/src/context/AppContext.js @@ -113,8 +113,8 @@ async function loadListFiles( res.uniqueFiles.forEach((f) => { res.totalUniqueZipSize += f.zipSize; }); - setListFiles(res); getFilesForUpdateDetails(res.uniqueFiles, setUpdateFiles, setTracksGroups); + setListFiles(res); const favFiles = await loadShareFiles(setShareWithMeFiles); const ownFavorites = TracksManager.getFavoriteGroups(res); const allFavorites = [...ownFavorites, ...favFiles]; @@ -169,7 +169,7 @@ export async function loadSmartFolders(setTracksGroups, listFiles) { async function getSmartFolders() { const res = await apiGet(`${process.env.REACT_APP_USER_API_SITE}/mapapi/create-smart-folders`, {}); if (res.ok) { - return await res.json(); + return res.json(); } return null; } @@ -702,6 +702,7 @@ export const AppContextProvider = (props) => { uniqueFiles: updatedUniqueFiles, }; }); + await loadSmartFolders(setTracksGroups, listFiles.uniqueFiles); } } }; @@ -709,7 +710,6 @@ export const AppContextProvider = (props) => { (async () => { await update(); setUpdateFiles(null); - await loadSmartFolders(setTracksGroups, listFiles.uniqueFiles); })(); }, [updateFiles]); diff --git a/map/src/frame/GlobalFrame.js b/map/src/frame/GlobalFrame.js index 0dba994235..57d04f23e8 100644 --- a/map/src/frame/GlobalFrame.js +++ b/map/src/frame/GlobalFrame.js @@ -366,8 +366,7 @@ const GlobalFrame = () => { if (!isEmpty(ctx.listFiles)) { const files = getGpxFiles(ctx.listFiles); const trackGroups = createTrackGroups({ files, ctx }); - const smartFolders = ctx.tracksGroups?.filter((g) => g.type === SMART_TYPE) || []; - ctx.setTracksGroups([...trackGroups, ...smartFolders]); + ctx.setTracksGroups(trackGroups); } else { const smartFolders = ctx.tracksGroups?.filter((g) => g.type === SMART_TYPE) || []; ctx.setTracksGroups(smartFolders); diff --git a/map/src/manager/track/SaveTrackManager.js b/map/src/manager/track/SaveTrackManager.js index fd0dd1773d..435a2a4b95 100644 --- a/map/src/manager/track/SaveTrackManager.js +++ b/map/src/manager/track/SaveTrackManager.js @@ -405,8 +405,7 @@ function updateTrackGroups(listFiles, ctx) { if (!isEmpty(listFiles)) { const files = getGpxFiles(listFiles); const trackGroups = createTrackGroups({ files, ctx }); - const smartFolders = ctx.tracksGroups?.filter((g) => g.type === SMART_TYPE) || []; - ctx.setTracksGroups([...trackGroups, ...smartFolders]); + ctx.setTracksGroups(trackGroups); } else { const smartFolders = ctx.tracksGroups?.filter((g) => g.type === SMART_TYPE) || []; ctx.setTracksGroups(smartFolders); diff --git a/map/src/manager/track/TracksManager.js b/map/src/manager/track/TracksManager.js index 626d31aa5b..bf3f715891 100644 --- a/map/src/manager/track/TracksManager.js +++ b/map/src/manager/track/TracksManager.js @@ -20,7 +20,7 @@ import anchorme from 'anchorme'; import { isVisibleTrack, updateVisibleCache } from '../../menu/visibletracks/VisibleTracks'; import { getFileStorage, GPX } from '../GlobalManager'; import { closeTrack } from './DeleteTrackManager'; -import { SHARE_TYPE } from '../../menu/share/shareConstants'; +import { SHARE_TYPE, SMART_TYPE } from '../../menu/share/shareConstants'; import { doSort } from '../../menu/actions/SortActions'; import { DEFAULT_SORT_METHOD } from '../../menu/tracks/TracksMenu'; import { TRACKS_KEY } from '../../util/hooks/menu/useRecentDataSaver'; @@ -611,6 +611,9 @@ export function createTrackGroups({ files, isSmartf = false, ctx }) { trackGroups.push(defaultGroup); } + const smartFolders = ctx.tracksGroups?.filter((g) => g.type === SMART_TYPE) || []; + trackGroups.push(...smartFolders); + addFilesAndCalculateLastModified(trackGroups); const sorted = doSort({ diff --git a/map/src/menu/tracks/TracksMenu.jsx b/map/src/menu/tracks/TracksMenu.jsx index 37036f3d7c..e0289344e0 100644 --- a/map/src/menu/tracks/TracksMenu.jsx +++ b/map/src/menu/tracks/TracksMenu.jsx @@ -65,15 +65,12 @@ export default function TracksMenu() { } else { setDefaultGroup(defaultGroupWithFolders); } - - const allGroups = defaultGroupWithFolders.subfolders || []; - doSort({ method: ctx.selectedSort?.tracks?.[DEFAULT_GROUP_NAME] ?? DEFAULT_SORT_METHOD, setSortFiles, setSortGroups, files: defGroup ? defGroup.groupFiles : [], - groups: allGroups, + groups: defaultGroupWithFolders.subfolders, }); } else { setDefaultGroup(defaultGroupWithFolders); From 867cc34fa4ccb5084a798bc8fb6d6448cef5dd7c Mon Sep 17 00:00:00 2001 From: Dima-1 Date: Tue, 3 Mar 2026 09:08:04 +0200 Subject: [PATCH 09/12] Rename --- map/src/context/AppContext.js | 4 ++-- map/src/manager/track/TracksManager.js | 16 ++++++++-------- map/src/menu/actions/SortActions.jsx | 4 ++-- map/src/menu/tracks/CloudTrackGroup.jsx | 2 +- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/map/src/context/AppContext.js b/map/src/context/AppContext.js index aa8fcea9aa..a9cc4bed2c 100644 --- a/map/src/context/AppContext.js +++ b/map/src/context/AppContext.js @@ -155,8 +155,8 @@ export async function loadSmartFolders(setTracksGroups, listFiles) { groupFiles: filesArray, files: filesArray, realSize: filesArray.length, - lastModifiedMs: minMs !== Infinity ? minMs : null, - lastModifiedData: minData, + minModifiedMs: minMs !== Infinity ? minMs : null, + minModifiedDate: minData, }; }); diff --git a/map/src/manager/track/TracksManager.js b/map/src/manager/track/TracksManager.js index bf3f715891..1071cf8d44 100644 --- a/map/src/manager/track/TracksManager.js +++ b/map/src/manager/track/TracksManager.js @@ -585,8 +585,8 @@ export function createTrackGroups({ files, isSmartf = false, ctx }) { name: folder, subfolders: [], groupFiles: [], - lastModifiedMs: null, - lastModifiedData: null, + minModifiedMs: null, + minModifiedDate: null, }; currentGroups.push(existingGroup); } @@ -604,8 +604,8 @@ export function createTrackGroups({ files, isSmartf = false, ctx }) { fullName: DEFAULT_GROUP_NAME, files: tracks, groupFiles: tracks, - lastModifiedMs: null, - lastModifiedData: null, + minModifiedMs: null, + minModifiedDate: null, }; defaultGroup.subfolders = trackGroups.filter((group) => group.name !== DEFAULT_GROUP_NAME); trackGroups.push(defaultGroup); @@ -657,8 +657,8 @@ function addFilesAndCalculateLastModified(groups) { function calculateLastModified(group) { if (!group.files || group.files.length === 0) { - group.lastModifiedMs = null; - group.lastModifiedData = null; + group.minModifiedMs = null; + group.minModifiedDate = null; return; } @@ -670,8 +670,8 @@ function calculateLastModified(group) { minData = file.updatetime; } } - group.lastModifiedMs = minMs; - group.lastModifiedData = minData; + group.minModifiedMs = minMs; + group.minModifiedDate = minData; } export function findGroupByName(groups, groupName) { diff --git a/map/src/menu/actions/SortActions.jsx b/map/src/menu/actions/SortActions.jsx index e3f1d34598..e232cb730e 100644 --- a/map/src/menu/actions/SortActions.jsx +++ b/map/src/menu/actions/SortActions.jsx @@ -33,8 +33,8 @@ function byAlpha(files, reverse) { export function byTime(files, reverse, isGroup = false) { if (isGroup) { return [...files].sort((a, b) => { - const A = a.lastModifiedMs; - const B = b.lastModifiedMs; + const A = a.minModifiedMs; + const B = b.minModifiedMs; if (A === B) { return az(a.name, b.name); } diff --git a/map/src/menu/tracks/CloudTrackGroup.jsx b/map/src/menu/tracks/CloudTrackGroup.jsx index 054878228c..068e8fdbdb 100644 --- a/map/src/menu/tracks/CloudTrackGroup.jsx +++ b/map/src/menu/tracks/CloudTrackGroup.jsx @@ -44,7 +44,7 @@ export default function CloudTrackGroup({ index, group }) { if (group.type === SMART_TYPE) { return `${group.realSize} ${t('shared_string_gpx_files').toLowerCase()}`; } - return `${fmt.monthShortDay(group.lastModifiedData)}, ${t('shared_string_gpx_files').toLowerCase()} ${group.realSize}`; + return `${fmt.monthShortDay(group.minModifiedDate)}, ${t('shared_string_gpx_files').toLowerCase()} ${group.realSize}`; }; return ( From 562211a42991aec301963c0a5d36dafb9689dc1e Mon Sep 17 00:00:00 2001 From: Dima-1 Date: Tue, 3 Mar 2026 09:59:32 +0200 Subject: [PATCH 10/12] Populate the list of files only when opening a SmartFolder --- map/src/context/AppContext.js | 58 +++++++++++++-------- map/src/manager/track/DeleteTrackManager.js | 2 +- map/src/manager/track/TracksManager.js | 17 +++--- map/src/menu/tracks/CloudTrackGroup.jsx | 25 +++++++-- map/src/menu/tracks/TrackGroupFolder.jsx | 3 +- 5 files changed, 71 insertions(+), 34 deletions(-) diff --git a/map/src/context/AppContext.js b/map/src/context/AppContext.js index a9cc4bed2c..65b10cc4ec 100644 --- a/map/src/context/AppContext.js +++ b/map/src/context/AppContext.js @@ -129,34 +129,20 @@ async function loadListFiles( } } -export async function loadSmartFolders(setTracksGroups, listFiles) { +export async function loadSmartFolders(setTracksGroups) { const res = await getSmartFolders(); const smartFolderGroups = (res ?? []).map((smartFolder) => { - const filesArray = []; - let minMs = Infinity; - let minData = null; - - (smartFolder.userFilePaths ?? []).forEach((path) => { - const file = listFiles?.find((f) => f.name === path); - if (file) { - filesArray.push({ ...file, smartFolder: true }); - if (file.updatetimems < minMs) { - minMs = file.updatetimems; - minData = file.updatetime; - } - } - }); - return { name: smartFolder.name, fullName: smartFolder.name, type: SMART_TYPE, subfolders: [], - groupFiles: filesArray, - files: filesArray, - realSize: filesArray.length, - minModifiedMs: minMs !== Infinity ? minMs : null, - minModifiedDate: minData, + groupFiles: [], + files: [], + realSize: smartFolder.userFilePaths?.length ?? 0, + minModifiedMs: null, + minModifiedDate: null, + userFilePaths: smartFolder.userFilePaths ?? [], }; }); @@ -166,6 +152,32 @@ export async function loadSmartFolders(setTracksGroups, listFiles) { }); } +export function populateSmartFolderFiles(smartFolder, listFiles) { + const filesArray = []; + let minMs = Infinity; + let minDate = null; + + (smartFolder.userFilePaths ?? []).forEach((path) => { + const file = listFiles?.find((f) => f.name === path); + if (file) { + filesArray.push({ ...file, smartFolder: true }); + if (file.updatetimems < minMs) { + minMs = file.updatetimems; + minDate = file.updatetime; + } + } + }); + + return { + ...smartFolder, + groupFiles: filesArray, + files: filesArray, + realSize: filesArray.length, + minModifiedMs: minMs !== Infinity ? minMs : null, + minModifiedDate: minDate, + }; +} + async function getSmartFolders() { const res = await apiGet(`${process.env.REACT_APP_USER_API_SITE}/mapapi/create-smart-folders`, {}); if (res.ok) { @@ -186,7 +198,7 @@ export async function getFilesForUpdateDetails(files, setUpdateFiles, setTracksG if (filesToUpdate.length > 0) { setUpdateFiles(filesToUpdate); } else { - await loadSmartFolders(setTracksGroups, files); + await loadSmartFolders(setTracksGroups); } } @@ -702,7 +714,7 @@ export const AppContextProvider = (props) => { uniqueFiles: updatedUniqueFiles, }; }); - await loadSmartFolders(setTracksGroups, listFiles.uniqueFiles); + await loadSmartFolders(setTracksGroups); } } }; diff --git a/map/src/manager/track/DeleteTrackManager.js b/map/src/manager/track/DeleteTrackManager.js index 894d5874ab..c668a0f476 100644 --- a/map/src/manager/track/DeleteTrackManager.js +++ b/map/src/manager/track/DeleteTrackManager.js @@ -85,7 +85,7 @@ async function deleteCloudFile(name, type, ctx) { } return { ...o }; }); - await loadSmartFolders(ctx.setTracksGroups, ctx.listFiles.uniqueFiles); + await loadSmartFolders(ctx.setTracksGroups); } } } diff --git a/map/src/manager/track/TracksManager.js b/map/src/manager/track/TracksManager.js index 1071cf8d44..7fdea01321 100644 --- a/map/src/manager/track/TracksManager.js +++ b/map/src/manager/track/TracksManager.js @@ -648,9 +648,14 @@ function addFilesAndCalculateLastModified(groups) { group.files.push(file); } }); - const directTracksCount = (group.groupFiles || []).filter((file) => !isPlaceholderFile(file)).length; - const subfoldersTracksCount = group.subfolders.reduce((acc, subfolder) => acc + (subfolder.realSize ?? 0), 0); - group.realSize = directTracksCount + subfoldersTracksCount; + + if (group.type === SMART_TYPE && group.userFilePaths && group.groupFiles.length === 0) { + group.realSize = group.userFilePaths.length; + } else { + const directTracksCount = (group.groupFiles || []).filter((file) => !isPlaceholderFile(file)).length; + const subfoldersTracksCount = group.subfolders.reduce((acc, subfolder) => acc + (subfolder.realSize ?? 0), 0); + group.realSize = directTracksCount + subfoldersTracksCount; + } calculateLastModified(group); }); } @@ -663,15 +668,15 @@ function calculateLastModified(group) { } let minMs = Infinity; - let minData = null; + let minDate = null; for (const file of group.files) { if (file.updatetimems < minMs) { minMs = file.updatetimems; - minData = file.updatetime; + minDate = file.updatetime; } } group.minModifiedMs = minMs; - group.minModifiedDate = minData; + group.minModifiedDate = minDate; } export function findGroupByName(groups, groupName) { diff --git a/map/src/menu/tracks/CloudTrackGroup.jsx b/map/src/menu/tracks/CloudTrackGroup.jsx index 068e8fdbdb..f910f91dbb 100644 --- a/map/src/menu/tracks/CloudTrackGroup.jsx +++ b/map/src/menu/tracks/CloudTrackGroup.jsx @@ -12,6 +12,7 @@ import DividerWithMargin from '../../frame/components/dividers/DividerWithMargin import ThreeDotsButton from '../../frame/components/btns/ThreeDotsButton'; import { fmt } from '../../util/dateFmt'; import { SMART_TYPE } from '../share/shareConstants'; +import { populateSmartFolderFiles } from '../../context/AppContext'; export default function CloudTrackGroup({ index, group }) { const ctx = useContext(AppContext); @@ -20,12 +21,23 @@ export default function CloudTrackGroup({ index, group }) { const [openActions, setOpenActions] = useState(false); const [processDownload, setProcessDownload] = useState(false); const anchorEl = useRef(null); + + const [populatedGroup, setPopulatedGroup] = useState(group); useEffect(() => { if (ctx.openedPopper && ctx.openedPopper !== anchorEl) { setOpenActions(false); } }, [ctx.openedPopper]); + + useEffect(() => { + if (group.type === SMART_TYPE && openActions && group.files?.length === 0) { + const populated = populateSmartFolderFiles(group, ctx.listFiles?.uniqueFiles); + setPopulatedGroup(populated); + } else { + setPopulatedGroup(group); + } + }, [openActions, group, ctx.listFiles?.uniqueFiles]); const getFolderIcon = () => { if (group.type === SMART_TYPE) { @@ -36,13 +48,20 @@ export default function CloudTrackGroup({ index, group }) { const handleClick = (e) => { if (e.target !== 'path') { - ctx.setOpenGroups((prevState) => [...prevState, group]); + let groupToOpen = group; + + if (group.type === SMART_TYPE) { + groupToOpen = populateSmartFolderFiles(group, ctx.listFiles?.uniqueFiles); + } + + ctx.setOpenGroups((prevState) => [...prevState, groupToOpen]); } }; const getInfoText = () => { if (group.type === SMART_TYPE) { - return `${group.realSize} ${t('shared_string_gpx_files').toLowerCase()}`; + const filesCount = group.userFilePaths?.length ?? 0; + return `${filesCount} ${t('shared_string_gpx_files').toLowerCase()}`; } return `${fmt.monthShortDay(group.minModifiedDate)}, ${t('shared_string_gpx_files').toLowerCase()} ${group.realSize}`; }; @@ -78,7 +97,7 @@ export default function CloudTrackGroup({ index, group }) { anchorEl={anchorEl} actions={ diff --git a/map/src/menu/tracks/TrackGroupFolder.jsx b/map/src/menu/tracks/TrackGroupFolder.jsx index a848790256..5299928a75 100644 --- a/map/src/menu/tracks/TrackGroupFolder.jsx +++ b/map/src/menu/tracks/TrackGroupFolder.jsx @@ -18,6 +18,7 @@ import { doSort } from '../actions/SortActions'; import isEmpty from 'lodash-es/isEmpty'; import { DEFAULT_SORT_METHOD } from './TracksMenu'; import Loading from '../errors/Loading'; +import { SMART_TYPE } from '../share/shareConstants'; export default function TrackGroupFolder({ folder = null, smartf = null }) { const ctx = useContext(AppContext); @@ -30,7 +31,7 @@ export default function TrackGroupFolder({ folder = null, smartf = null }) { // update group after changing or deleting inner tracks useEffect(() => { - if (ctx.tracksGroups && folder) { + if (ctx.tracksGroups && folder && folder.type !== SMART_TYPE) { let found = findGroupByName(ctx.tracksGroups, group.fullName); if (ctx.openGroups && ctx.openGroups.length > 0) { const updatedOpenGroups = [...ctx.openGroups]; From 21d334f7f570fd207842d18030981dbaa9329bbb Mon Sep 17 00:00:00 2001 From: Dima-1 Date: Tue, 3 Mar 2026 10:15:31 +0200 Subject: [PATCH 11/12] Fix format remove loadSmartFolders call from getFilesForUpdateDetails --- map/src/context/AppContext.js | 2 -- map/src/manager/track/TracksManager.js | 7 +++++-- map/src/menu/tracks/CloudTrackGroup.jsx | 8 ++++---- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/map/src/context/AppContext.js b/map/src/context/AppContext.js index 65b10cc4ec..bef386e53e 100644 --- a/map/src/context/AppContext.js +++ b/map/src/context/AppContext.js @@ -197,8 +197,6 @@ export async function getFilesForUpdateDetails(files, setUpdateFiles, setTracksG })); if (filesToUpdate.length > 0) { setUpdateFiles(filesToUpdate); - } else { - await loadSmartFolders(setTracksGroups); } } diff --git a/map/src/manager/track/TracksManager.js b/map/src/manager/track/TracksManager.js index 7fdea01321..7ad244db6d 100644 --- a/map/src/manager/track/TracksManager.js +++ b/map/src/manager/track/TracksManager.js @@ -648,12 +648,15 @@ function addFilesAndCalculateLastModified(groups) { group.files.push(file); } }); - + if (group.type === SMART_TYPE && group.userFilePaths && group.groupFiles.length === 0) { group.realSize = group.userFilePaths.length; } else { const directTracksCount = (group.groupFiles || []).filter((file) => !isPlaceholderFile(file)).length; - const subfoldersTracksCount = group.subfolders.reduce((acc, subfolder) => acc + (subfolder.realSize ?? 0), 0); + const subfoldersTracksCount = group.subfolders.reduce( + (acc, subfolder) => acc + (subfolder.realSize ?? 0), + 0 + ); group.realSize = directTracksCount + subfoldersTracksCount; } calculateLastModified(group); diff --git a/map/src/menu/tracks/CloudTrackGroup.jsx b/map/src/menu/tracks/CloudTrackGroup.jsx index f910f91dbb..c222f9fa2d 100644 --- a/map/src/menu/tracks/CloudTrackGroup.jsx +++ b/map/src/menu/tracks/CloudTrackGroup.jsx @@ -21,7 +21,7 @@ export default function CloudTrackGroup({ index, group }) { const [openActions, setOpenActions] = useState(false); const [processDownload, setProcessDownload] = useState(false); const anchorEl = useRef(null); - + const [populatedGroup, setPopulatedGroup] = useState(group); useEffect(() => { @@ -29,7 +29,7 @@ export default function CloudTrackGroup({ index, group }) { setOpenActions(false); } }, [ctx.openedPopper]); - + useEffect(() => { if (group.type === SMART_TYPE && openActions && group.files?.length === 0) { const populated = populateSmartFolderFiles(group, ctx.listFiles?.uniqueFiles); @@ -49,11 +49,11 @@ export default function CloudTrackGroup({ index, group }) { const handleClick = (e) => { if (e.target !== 'path') { let groupToOpen = group; - + if (group.type === SMART_TYPE) { groupToOpen = populateSmartFolderFiles(group, ctx.listFiles?.uniqueFiles); } - + ctx.setOpenGroups((prevState) => [...prevState, groupToOpen]); } }; From 12a4495a37e8da997a9f4effa75362bddbaf5489 Mon Sep 17 00:00:00 2001 From: Dima-1 Date: Tue, 3 Mar 2026 23:38:49 +0200 Subject: [PATCH 12/12] Rename and fix lastModifiedDate --- map/src/context/AppContext.js | 26 ++++++++++++------------- map/src/manager/track/TracksManager.js | 26 ++++++++++++------------- map/src/menu/actions/SortActions.jsx | 4 ++-- map/src/menu/tracks/CloudTrackGroup.jsx | 2 +- map/src/menu/tracks/TracksMenu.jsx | 2 +- 5 files changed, 30 insertions(+), 30 deletions(-) diff --git a/map/src/context/AppContext.js b/map/src/context/AppContext.js index bef386e53e..76a04054bf 100644 --- a/map/src/context/AppContext.js +++ b/map/src/context/AppContext.js @@ -115,6 +115,7 @@ async function loadListFiles( }); getFilesForUpdateDetails(res.uniqueFiles, setUpdateFiles, setTracksGroups); setListFiles(res); + loadSmartFolders(setTracksGroups); const favFiles = await loadShareFiles(setShareWithMeFiles); const ownFavorites = TracksManager.getFavoriteGroups(res); const allFavorites = [...ownFavorites, ...favFiles]; @@ -140,8 +141,8 @@ export async function loadSmartFolders(setTracksGroups) { groupFiles: [], files: [], realSize: smartFolder.userFilePaths?.length ?? 0, - minModifiedMs: null, - minModifiedDate: null, + lastModifiedMs: null, + lastModifiedDate: null, userFilePaths: smartFolder.userFilePaths ?? [], }; }); @@ -154,16 +155,16 @@ export async function loadSmartFolders(setTracksGroups) { export function populateSmartFolderFiles(smartFolder, listFiles) { const filesArray = []; - let minMs = Infinity; - let minDate = null; + let maxMs = -Infinity; + let maxDate = null; (smartFolder.userFilePaths ?? []).forEach((path) => { const file = listFiles?.find((f) => f.name === path); if (file) { filesArray.push({ ...file, smartFolder: true }); - if (file.updatetimems < minMs) { - minMs = file.updatetimems; - minDate = file.updatetime; + if (file.updatetimems > maxMs) { + maxMs = file.updatetimems; + maxDate = file.updatetime; } } }); @@ -173,8 +174,8 @@ export function populateSmartFolderFiles(smartFolder, listFiles) { groupFiles: filesArray, files: filesArray, realSize: filesArray.length, - minModifiedMs: minMs !== Infinity ? minMs : null, - minModifiedDate: minDate, + lastModifiedMs: maxMs, + lastModifiedDate: maxDate, }; } @@ -186,7 +187,7 @@ async function getSmartFolders() { return null; } -export async function getFilesForUpdateDetails(files, setUpdateFiles, setTracksGroups) { +export function getFilesForUpdateDetails(files, setUpdateFiles) { const filesToUpdate = files .filter((f) => f.details && f.details.update && f.type === GPX && f.name.toLowerCase().endsWith(GPX_FILE_EXT)) .map((f) => ({ @@ -717,10 +718,9 @@ export const AppContextProvider = (props) => { } }; - (async () => { - await update(); + update().then(() => { setUpdateFiles(null); - })(); + }); }, [updateFiles]); useEffect(() => { diff --git a/map/src/manager/track/TracksManager.js b/map/src/manager/track/TracksManager.js index 7ad244db6d..c1fff86d21 100644 --- a/map/src/manager/track/TracksManager.js +++ b/map/src/manager/track/TracksManager.js @@ -585,8 +585,8 @@ export function createTrackGroups({ files, isSmartf = false, ctx }) { name: folder, subfolders: [], groupFiles: [], - minModifiedMs: null, - minModifiedDate: null, + lastModifiedMs: null, + lastModifiedDate: null, }; currentGroups.push(existingGroup); } @@ -604,8 +604,8 @@ export function createTrackGroups({ files, isSmartf = false, ctx }) { fullName: DEFAULT_GROUP_NAME, files: tracks, groupFiles: tracks, - minModifiedMs: null, - minModifiedDate: null, + lastModifiedMs: null, + lastModifiedDate: null, }; defaultGroup.subfolders = trackGroups.filter((group) => group.name !== DEFAULT_GROUP_NAME); trackGroups.push(defaultGroup); @@ -665,21 +665,21 @@ function addFilesAndCalculateLastModified(groups) { function calculateLastModified(group) { if (!group.files || group.files.length === 0) { - group.minModifiedMs = null; - group.minModifiedDate = null; + group.lastModifiedMs = null; + group.lastModifiedDate = null; return; } - let minMs = Infinity; - let minDate = null; + let maxMs = -Infinity; + let maxDate = null; for (const file of group.files) { - if (file.updatetimems < minMs) { - minMs = file.updatetimems; - minDate = file.updatetime; + if (file.updatetimems > maxMs) { + maxMs = file.updatetimems; + maxDate = file.updatetime; } } - group.minModifiedMs = minMs; - group.minModifiedDate = minDate; + group.lastModifiedMs = maxMs; + group.lastModifiedDate = maxDate; } export function findGroupByName(groups, groupName) { diff --git a/map/src/menu/actions/SortActions.jsx b/map/src/menu/actions/SortActions.jsx index e232cb730e..e3f1d34598 100644 --- a/map/src/menu/actions/SortActions.jsx +++ b/map/src/menu/actions/SortActions.jsx @@ -33,8 +33,8 @@ function byAlpha(files, reverse) { export function byTime(files, reverse, isGroup = false) { if (isGroup) { return [...files].sort((a, b) => { - const A = a.minModifiedMs; - const B = b.minModifiedMs; + const A = a.lastModifiedMs; + const B = b.lastModifiedMs; if (A === B) { return az(a.name, b.name); } diff --git a/map/src/menu/tracks/CloudTrackGroup.jsx b/map/src/menu/tracks/CloudTrackGroup.jsx index c222f9fa2d..03970783fc 100644 --- a/map/src/menu/tracks/CloudTrackGroup.jsx +++ b/map/src/menu/tracks/CloudTrackGroup.jsx @@ -63,7 +63,7 @@ export default function CloudTrackGroup({ index, group }) { const filesCount = group.userFilePaths?.length ?? 0; return `${filesCount} ${t('shared_string_gpx_files').toLowerCase()}`; } - return `${fmt.monthShortDay(group.minModifiedDate)}, ${t('shared_string_gpx_files').toLowerCase()} ${group.realSize}`; + return `${fmt.monthShortDay(group.lastModifiedDate)}, ${t('shared_string_gpx_files').toLowerCase()} ${group.realSize}`; }; return ( diff --git a/map/src/menu/tracks/TracksMenu.jsx b/map/src/menu/tracks/TracksMenu.jsx index e0289344e0..57fe8cb8b2 100644 --- a/map/src/menu/tracks/TracksMenu.jsx +++ b/map/src/menu/tracks/TracksMenu.jsx @@ -161,7 +161,7 @@ export default function TracksMenu() { )} {ctx.tracksGroups && - (sortGroups?.length > 0 ? sortGroups : ctx.tracksGroups) + (sortGroups && sortGroups.length > 0 ? sortGroups : ctx.tracksGroups) .filter((g) => g.name !== DEFAULT_GROUP_NAME) .map((group, index) => { return ;