diff --git a/lib/maptool/gpx.rb b/lib/maptool/gpx.rb index b967c01..81f281d 100644 --- a/lib/maptool/gpx.rb +++ b/lib/maptool/gpx.rb @@ -8,6 +8,19 @@ module Maptool module Gpx class ParseError < StandardError; end + # Cheap content-type check for the file tree's icons: a plain substring + # search rather than a full REXML parse, since some uploaded files (e.g. + # a several-thousand-waypoint POI export) are large enough that parsing + # the whole DOM just to answer "does it have any of these tags" would + # make every tree fetch noticeably slower. + def self.kinds(path) + content = File.read(path) + kinds = [] + kinds << "waypoint" if content.include?(" "folder", "name" => entry, "path" => rel, "children" => list_children(full, rel) } elsif entry.downcase.end_with?(".gpx") - { "type" => "file", "name" => entry, "path" => rel } + { "type" => "file", "name" => entry, "path" => rel, "kinds" => Gpx.kinds(full) } end end end diff --git a/public/css/style.css b/public/css/style.css index 2d040f3..5618662 100644 --- a/public/css/style.css +++ b/public/css/style.css @@ -292,6 +292,11 @@ html, body { } .gpx-tree { + /* flex:1 (not just overflow-y) so the tree fills the rest of the panel + even when short/empty — otherwise the blank space below it wouldn't + be part of the drop target and dropping a file there would silently + do nothing. */ + flex: 1; overflow-y: auto; padding: 8px 12px; } @@ -342,12 +347,23 @@ html, body { cursor: pointer; } -.gpx-tree__file label span { +.gpx-tree__file-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } +.gpx-tree__kind-icon { + flex-shrink: 0; + display: flex; + color: #999; +} + +.gpx-tree__kind-icon svg { + width: 14px; + height: 14px; +} + .gpx-tree__delete { flex-shrink: 0; border: none; diff --git a/public/js/gpx.js b/public/js/gpx.js index 5f284c5..cba6b06 100644 --- a/public/js/gpx.js +++ b/public/js/gpx.js @@ -1,5 +1,5 @@ import { Popup } from "/vendor/maplibre-gl/maplibre-gl.mjs"; -import { GPX_ICON, NEW_FOLDER_ICON, UPLOAD_ICON } from "/js/icons.js"; +import { GPX_ICON, NEW_FOLDER_ICON, UPLOAD_ICON, WAYPOINT_ICON, TRACK_ICON } from "/js/icons.js"; import { notifySidePanelOpen, onlyOneSidePanelOpen } from "/js/panels.js"; const TREE_URL = "/api/gpx/tree"; @@ -205,9 +205,11 @@ export class GpxPanelControl { checkbox.checked = this._visible.has(file.path); checkbox.addEventListener("change", () => this._toggleFile(file.path, checkbox.checked)); const name = document.createElement("span"); + name.className = "gpx-tree__file-name"; name.textContent = file.name; name.title = file.name; label.appendChild(checkbox); + (file.kinds || []).forEach((kind) => label.appendChild(this._kindIcon(kind))); label.appendChild(name); row.appendChild(label); row.appendChild(this._deleteButton(() => this._deleteFile(file.path))); @@ -215,6 +217,19 @@ export class GpxPanelControl { return row; } + _kindIcon(kind) { + const icon = document.createElement("span"); + icon.className = "gpx-tree__kind-icon"; + if (kind === "waypoint") { + icon.innerHTML = WAYPOINT_ICON; + icon.title = "Has waypoints"; + } else { + icon.innerHTML = TRACK_ICON; + icon.title = "Has a track"; + } + return icon; + } + _deleteButton(onClick) { const button = document.createElement("button"); button.type = "button"; diff --git a/public/js/icons.js b/public/js/icons.js index 9a6d5bd..610264f 100644 --- a/public/js/icons.js +++ b/public/js/icons.js @@ -37,3 +37,13 @@ export const UPLOAD_ICON = ` `; + +// Shown next to each file in the GPX tree, indicating whether it has +// waypoints, a track, or both. +export const WAYPOINT_ICON = ` + + + +`; + +export const TRACK_ICON = GPX_ICON;