-
Notifications
You must be signed in to change notification settings - Fork 372
Smart folders simple UI #1459
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Smart folders simple UI #1459
Changes from all commits
3551e80
b16d5c6
26d8a7a
c438756
6524591
ef1e304
7035c73
bd28fe1
867cc34
562211a
21d334f
12a4495
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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,6 +96,7 @@ async function loadListFiles( | |
| setProcessingGroups, | ||
| setVisibleTracks, | ||
| setShareWithMeFiles, | ||
| setTracksGroups, | ||
| setUpdateFiles | ||
| ) { | ||
| if (loginUser !== listFiles.loginUser) { | ||
|
|
@@ -111,8 +113,9 @@ async function loadListFiles( | |
| res.uniqueFiles.forEach((f) => { | ||
| res.totalUniqueZipSize += f.zipSize; | ||
| }); | ||
| getFilesForUpdateDetails(res.uniqueFiles, setUpdateFiles); | ||
| getFilesForUpdateDetails(res.uniqueFiles, setUpdateFiles, setTracksGroups); | ||
| setListFiles(res); | ||
| loadSmartFolders(setTracksGroups); | ||
| const favFiles = await loadShareFiles(setShareWithMeFiles); | ||
| const ownFavorites = TracksManager.getFavoriteGroups(res); | ||
| const allFavorites = [...ownFavorites, ...favFiles]; | ||
|
|
@@ -127,6 +130,63 @@ async function loadListFiles( | |
| } | ||
| } | ||
|
|
||
| export async function loadSmartFolders(setTracksGroups) { | ||
| const res = await getSmartFolders(); | ||
| const smartFolderGroups = (res ?? []).map((smartFolder) => { | ||
| return { | ||
| name: smartFolder.name, | ||
| fullName: smartFolder.name, | ||
| type: SMART_TYPE, | ||
| subfolders: [], | ||
| groupFiles: [], | ||
| files: [], | ||
| realSize: smartFolder.userFilePaths?.length ?? 0, | ||
| lastModifiedMs: null, | ||
| lastModifiedDate: null, | ||
| userFilePaths: smartFolder.userFilePaths ?? [], | ||
| }; | ||
| }); | ||
|
|
||
| setTracksGroups((prev) => { | ||
| const withoutSmartFolders = prev.filter((g) => g.type !== SMART_TYPE); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Duplication of logic |
||
| return [...withoutSmartFolders, ...smartFolderGroups]; | ||
| }); | ||
| } | ||
|
|
||
| export function populateSmartFolderFiles(smartFolder, listFiles) { | ||
| const filesArray = []; | ||
| let maxMs = -Infinity; | ||
| let maxDate = null; | ||
|
|
||
| (smartFolder.userFilePaths ?? []).forEach((path) => { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why not sort the files on the backend? |
||
| const file = listFiles?.find((f) => f.name === path); | ||
| if (file) { | ||
| filesArray.push({ ...file, smartFolder: true }); | ||
| if (file.updatetimems > maxMs) { | ||
| maxMs = file.updatetimems; | ||
| maxDate = file.updatetime; | ||
| } | ||
| } | ||
| }); | ||
|
|
||
| return { | ||
| ...smartFolder, | ||
| groupFiles: filesArray, | ||
| files: filesArray, | ||
| realSize: filesArray.length, | ||
| lastModifiedMs: maxMs, | ||
| lastModifiedDate: maxDate, | ||
| }; | ||
| } | ||
|
|
||
| async function getSmartFolders() { | ||
| const res = await apiGet(`${process.env.REACT_APP_USER_API_SITE}/mapapi/create-smart-folders`, {}); | ||
| if (res.ok) { | ||
| return res.json(); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Take the date right away |
||
| } | ||
| 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)) | ||
|
|
@@ -653,6 +713,7 @@ export const AppContextProvider = (props) => { | |
| uniqueFiles: updatedUniqueFiles, | ||
| }; | ||
| }); | ||
| await loadSmartFolders(setTracksGroups); | ||
| } | ||
| } | ||
| }; | ||
|
|
@@ -676,6 +737,7 @@ export const AppContextProvider = (props) => { | |
| setProcessingGroups, | ||
| setVisibleTracks, | ||
| setShareWithMeFiles, | ||
| setTracksGroups, | ||
| setUpdateFiles | ||
| ).then(() => { | ||
| setGpxLoading(false); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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'; | ||
|
|
@@ -367,7 +368,8 @@ const GlobalFrame = () => { | |
| const trackGroups = createTrackGroups({ files, ctx }); | ||
| ctx.setTracksGroups(trackGroups); | ||
| } else { | ||
| ctx.setTracksGroups([]); | ||
| const smartFolders = ctx.tracksGroups?.filter((g) => g.type === SMART_TYPE) || []; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There are empty groups here where smartFolders come from. |
||
| ctx.setTracksGroups(smartFolders); | ||
| } | ||
| }, [ctx.listFiles, ctx.selectedSort]); | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -17,7 +17,8 @@ import FavoritesManager, { DEFAULT_FAV_GROUP_NAME } from '../../manager/Favorite | |
| 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'; | ||
| import { DEFAULT_GROUP_NAME } from '../../manager/track/TracksManager'; | ||
|
|
||
| const az = (a, b) => (a > b) - (a < b); | ||
|
|
||
|
|
@@ -254,6 +255,10 @@ const SortActions = forwardRef( | |
|
|
||
| const groups = () => { | ||
| if (trackGroup) { | ||
| if (trackGroup.name === DEFAULT_GROUP_NAME || trackGroup.fullName === DEFAULT_GROUP_NAME) { | ||
| const smartFolders = ctx.tracksGroups?.filter((g) => g.type === SMART_TYPE) || []; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Duplication of logic |
||
| return [...(trackGroup.subfolders || []), ...smartFolders]; | ||
| } | ||
| return trackGroup.subfolders; | ||
| } else if (favoriteGroup) { | ||
| if (smartf?.type === SHARE_TYPE) { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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,8 @@ 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'; | ||
| import { populateSmartFolderFiles } from '../../context/AppContext'; | ||
|
|
||
| export default function CloudTrackGroup({ index, group }) { | ||
| const ctx = useContext(AppContext); | ||
|
|
@@ -19,31 +22,63 @@ export default function CloudTrackGroup({ index, group }) { | |
| 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]); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The list of files for filters should only be fetched when a specific folder is opened. The cache should be stored after the first read, and cleared only when either listFiles changes or the context is updated. |
||
|
|
||
| const getFolderIcon = () => { | ||
| if (group.type === SMART_TYPE) { | ||
| return <SmartIcon />; | ||
| } | ||
| return <FolderIcon />; | ||
| }; | ||
|
|
||
| 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]); | ||
| } | ||
| }; | ||
|
|
||
| const getInfoText = () => { | ||
| if (group.type === SMART_TYPE) { | ||
| const filesCount = group.userFilePaths?.length ?? 0; | ||
| return `${filesCount} ${t('shared_string_gpx_files').toLowerCase()}`; | ||
| } | ||
| return `${fmt.monthShortDay(group.lastModifiedDate)}, ${t('shared_string_gpx_files').toLowerCase()} ${group.realSize}`; | ||
| }; | ||
|
|
||
| return ( | ||
| <> | ||
| <MenuItem | ||
| className={styles.group} | ||
| key={'group' + group.name + index} | ||
| id={'se-menu-cloud-' + group.name} | ||
| onClick={(e) => { | ||
| if (e.target !== 'path') { | ||
| ctx.setOpenGroups((prevState) => [...prevState, group]); | ||
| } | ||
| }} | ||
| onClick={handleClick} | ||
| > | ||
| <ListItemIcon className={styles.icon}> | ||
| <FolderIcon /> | ||
| </ListItemIcon> | ||
| <ListItemIcon className={styles.icon}>{getFolderIcon()}</ListItemIcon> | ||
| <ListItemText> | ||
| <MenuItemWithLines name={group.name} maxLines={2} /> | ||
| <Typography variant="body2" className={styles.groupInfo} noWrap> | ||
| {`${fmt.monthShortDay(group.lastModifiedData)}, ${t('shared_string_gpx_files').toLowerCase()} ${group.realSize}`} | ||
| {getInfoText()} | ||
| </Typography> | ||
| </ListItemText> | ||
| <ThreeDotsButton | ||
|
|
@@ -62,7 +97,7 @@ export default function CloudTrackGroup({ index, group }) { | |
| anchorEl={anchorEl} | ||
| actions={ | ||
| <GroupActions | ||
| group={group} | ||
| group={populatedGroup} | ||
| setOpenActions={setOpenActions} | ||
| setProcessDownload={setProcessDownload} | ||
| /> | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.