Skip to content

Improved drag and drop#1799

Open
Dima-1 wants to merge 4 commits into
mainfrom
ui-drag-and-drop
Open

Improved drag and drop#1799
Dima-1 wants to merge 4 commits into
mainfrom
ui-drag-and-drop

Conversation

@Dima-1

@Dima-1 Dima-1 commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Implements an improved drag-and-drop flow for importing track files into OsmAnd Cloud, including visual drop overlays and folder-targeted dropping from the Tracks UI.

Changes:

  • Added a global GPX drag controller + geometry helpers to resolve drop targets (map vs tracks menu folders) and show contextual overlays.
  • Refactored Cloud GPX/KMZ/KML import into a shared useCloudGpxImport hook and reused it from both drag-and-drop and the existing uploader.
  • Enhanced UX by styling drop targets in the Tracks menu and enabling “zoom to track” after successful upload/open.

Reviewed changes

Copilot reviewed 16 out of 16 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
map/src/util/hooks/useCloudGpxImport.js New shared hook for importing GPX/KMZ/KML into OsmAnd Cloud.
map/src/menu/tracks/TracksMenu.jsx Marks the tracks list container as a drop target (data attribute + styling hook).
map/src/menu/tracks/TrackGroupFolder.jsx Marks opened folders as drop targets for folder-specific imports.
map/src/menu/tracks/CloudTrackGroup.jsx Adds drop-hover styling and folder targeting metadata for groups.
map/src/menu/trackfavmenu.module.css Adds drop-target styles for folders/groups.
map/src/map/layers/LocalClientTrackLayer.js Removes the old map-only drag/drop handling in favor of the global controller.
map/src/map/components/GpxMenuDropOverlay.jsx New overlay for menu-drop guidance (positioned to menu region).
map/src/map/components/gpxMapDropOverlay.module.css Updates overlay styling and renames .overlay.dropOverlay.
map/src/map/components/GpxMapDropOverlay.jsx Converts overlay to use AppContext drag state + computed insets.
map/src/map/components/GpxMapDropGeometry.js New helper module for computing visible map/menu drop geometry + target resolution.
map/src/map/components/GpxFileDragController.jsx New global window-level drag/drop controller, triggers cloud import into resolved folder.
map/src/manager/track/TracksManager.js Adds IMPORT_FOLDER_NAME constant used for map-drop default folder.
map/src/manager/track/SaveTrackManager.js Sets zoomToTrack when opening a just-uploaded track.
map/src/frame/util/CloudGpxUploader.jsx Refactors uploader to use the shared cloud import hook.
map/src/frame/GlobalFrame.js Mounts the global drag controller and moves main menu open-state to AppContext.
map/src/context/AppContext.js Adds openMainMenu and gpxFileDrag to shared app context.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +65 to +87
const reader = new FileReader();
reader.addEventListener('load', (e) => {
const data = e.target.result;
if (data) {
mutateUploadedFiles(
(o) =>
(o[file.name] = {
file,
selected,
data: data,
name: file.name,
originalName: file.name,
folder,
})
);
} else {
ctx.setTrackErrorMsg({
title: 'Import error',
msg: `Unable to import ${file.name}`,
});
ctx.setTrackLoading([...ctx.trackLoading.filter((n) => n !== file.name)]);
}
});

@alisa911 alisa911 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rename all the new files away from the gpx prefix, the feature handles not only GPX (also KMZ/KML), but only tracks (not favorites). Also move these files out of map/ into frame/, since they're not map-specific.


const currentCtx = ctxRef.current;
const { hoverFolder, overMap } = resolveGpxDropTarget(e.clientX, e.clientY, currentCtx);
currentCtx.setGpxFileDrag({ active: true, hoverFolder, overMap });

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

setGpxFileDrag is called with a fresh object on every dragover, spamming context updates and re-rendering on each mouse move

const currentCtx = ctxRef.current;
const { hoverFolder, overMap } = resolveGpxDropTarget(e.clientX, e.clientY, currentCtx);
currentCtx.setGpxFileDrag({ active: true, hoverFolder, overMap });
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the overlay is activated regardless of auth, so free/anonymous users see "Drop GPX to import" but the drop silently no-ops

}

const selected = files.length === 1;
ctx.setTrackLoading(files.map((track) => removeFileExtension(track.name) + GPX_FILE_EXT));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

loading entries are set as name + GPX_FILE_EXT but removed by raw file.name on error, so failed .kmz/.kml imports leave a stuck loading placeholder

import { createTrackFreeName, removeFileExtension, saveTrackToCloud } from '../../manager/track/SaveTrackManager';
import { useMutator } from '../Utils';

const CLOUD_TRACK_EXTENSIONS = ['.gpx', '.kmz', '.kml'];

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.kml is accepted but always saved with type: 'GPX' and read as text

export const DEFAULT_GROUP_NAME = '';
export const IMPORT_FOLDER_NAME = 'Import';

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMPORT_FOLDER_NAME = 'Import' is a hardcoded, untranslated cloud-folder name; verify it matches existing folder-naming conventions

export default function GpxMenuDropOverlay() {
const ctx = useContext(AppContext);
const { t } = useTranslation();
const ctxRef = useRef(ctx);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const ctxRef = useRef(ctx) violates the project rule "refs are a last resort" (CLAUDE.md), and stashing the entire context in a ref is exactly what we don't do.

Comment thread map/src/map/components/GpxMenuDropOverlay.jsx
const ctx = useContext(AppContext);
const { t } = useTranslation();
const ctxRef = useRef(ctx);
const [overlayStyle, setOverlayStyle] = useState(null);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The hovered folder is already highlighted via the groupDropTarget/folderDropTarget CSS classes; a separately measured portal overlay on top of the same folder duplicates that visual feedback. Pick one mechanism.

return;
}

const update = () => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Locating and measuring the target folder via getElementById to position a fixed overlay couples this to DOM ids and is brittle; prefer rendering the highlight inside the folder component itself.

return (
<>
<Box
id={`se-tracks-folder`}
className={isDropTarget ? styles.folderDropTarget : undefined}
data-cloud-track-folder={isDropTarget ? group.fullName : undefined}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

data-cloud-track-folder exists only to bridge the global window drag listener (which has just cursor coords) back to a folder identity via elementFromPoint(...).closest(...). This is DOM-scraping for state React already owns. Attach onDragEnter/onDragOver/onDrop to the folder components (they already close over group.fullName) and set the hover target in context, removing the data-* stamping entirely. If kept short-term, at least extract the attribute name into a shared constant and document the ""-means-root sentinel.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants