Add GPX drag & drop, folder management, delete, and folder-first sort

Uploading is now drag & drop (or click to browse) instead of a form;
folders can be created any time, not just during upload; files can be
dragged onto a folder to move them, or dropped elsewhere in the tree
to move back to the top level; files and folders can be deleted. Both
drag sources (an OS file drag and dragging an existing tree entry)
land on the same drop targets, told apart by a custom drag payload
type versus the browser's own "Files" type.

Also sort folders before files in the tree (previously interleaved
alphabetically).
This commit is contained in:
randy committed 2026-08-25 15:14:09 +02:00
1 parent 4e0d2abbd0
commit d813dd0b20
6 files changed
+440 -66

No files matched your search

+26 -9
View File
@@ -116,20 +116,37 @@ terms, the API is free for personal/non-commercial use.
== GPX tracks and waypoints
The "GPX" map control (`public/js/gpx.js`) opens a side panel to upload
GPX files and toggle them on the map, individually.
The "GPX" map control (`public/js/gpx.js`) opens a side panel to
upload GPX files by dragging them in, organize them into folders (also
by dragging), and toggle each one on the map individually.
Storage:: Uploaded files are stored as-is under `data/gpx/`, in
whatever folder hierarchy the user types into the upload form (e.g.
`Hiking/2026`) — `Maptool::GpxStore`
(`lib/maptool/gpx_store.rb`) creates folders as needed. Every path
coming from the client (upload folder, file path in a URL) is
Storage:: Files are stored as-is under `data/gpx/`, in a
folder hierarchy the user manages directly — `Maptool::GpxStore`
(`lib/maptool/gpx_store.rb`) provides `save`, `create_folder`,
`delete_file`, `delete_folder` and `move`. Every path coming from the
client (a folder to create/upload/move into, a file path in a URL) is
resolved with `File.expand_path` and checked against the store root
before touching the filesystem, so `../..`-style path traversal can't
escape `data/gpx/`.
escape `data/gpx/`; `delete_folder` additionally refuses an empty path
or one that resolves to the store root itself, so it can never `rm
-rf` the whole store.
`GET /api/gpx/tree` returns the resulting folder/file tree as JSON,
which the panel renders recursively (folders as `<details>`, files as
a checkbox).
a checkbox, both with a delete button).
Drag & drop:: The dropzone at the top of the panel accepts one or more
`.gpx` files (`POST /api/gpx`, multipart) into the top level, or click
it to browse instead. An already-uploaded file (a
`.gpx-tree__file` row, `draggable`) can be dragged onto a folder to
move it there (`POST /api/gpx/move`), or dropped anywhere else in the
tree to move it back to the top level; an OS file can also be dropped
directly onto a folder to upload straight into it. Both drag sources
land on the same drop targets and are told apart by a custom
`application/x-gpx-path` drag payload type versus the browser's own
`"Files"` type. If a file that's currently checked (visible on the
map) gets moved, its map layers are re-added under the new path so it
stays visible; if it (or its containing folder) gets deleted, its
layers are removed instead.
Rendering:: Checking a file fetches `GET
/api/gpx/file/<path>` — `Maptool::Gpx` (`lib/maptool/gpx.rb`) parses