Repository navigation
Port to maplibre gl - #1699
Merged
Merged
Port to maplibre gl#1699
Conversation
Step 1 of the Leaflet -> vue-maplibre-gl port. Map.vue now renders via MglMap instead of L.map(), rewritten in <script setup lang="ts">. - Base map: street (OpenFreeMap "Liberty" vector style, no API key) and satellite (ESRI World Imagery raster, unchanged URL) via a small MglCustomControl toggle, replacing the old street/satellite easyButtons. - Native MapLibre terrain (raster-dem, Mapzen Terrarium tiles) toggle. - Dropped support for switching between many base layers (topographic, watercolor, roads overlay, the layers-control UI) - no longer needed. - src/data/mapLayers.ts: added typed MapLibre style/terrain-source definitions; kept the legacy Leaflet-flavoured exports (LayerIds, Layers, baseLayersByName, ...) since MapContainer.vue (public favorite share page) is still Leaflet-based and depends on them until it's ported too. - Removed now-fully-unused deps: @maplibre/maplibre-gl-leaflet, @raruto/leaflet-elevation, leaflet-contextmenu, leaflet-easybutton, leaflet.locatecontrol, mapbox-gl, mapbox-gl-leaflet, pmtiles. - Added vue-maplibre-gl and @maplibre/maplibre-gl-directions (for a later routing step). - .eslintrc.cjs: use @typescript-eslint/parser for .vue files so <script setup lang="ts"> parses. Not ported yet (left for follow-up steps, on purpose): favorites/photos/tracks/devices/contacts markers and clustering, routing (will use @maplibre/maplibre-gl-directions), the right-click context menu, search box / history control, and the track elevation profile chart. Map.vue's children rendering those features are temporarily not mounted. Assisted-by: ClaudeCode:claude-sonnet-5 Signed-off-by: Carl Schwan <carl@carlschwan.eu>
MapLibre GL JS always fetches its sources (style JSON, vector tiles, sprites, glyphs, raster tiles, terrain DEM tiles) via fetch()/XHR, even for raster sources - unlike Leaflet's <img> tags, these fall under connect-src rather than img-src. Without this, loading the OpenFreeMap style, the ESRI satellite raster tiles, or the terrain DEM tiles failed with a CSP-driven NetworkError. Also switched the terrain DEM source to the bucket-specific elevation-tiles-prod.s3.amazonaws.com endpoint instead of the shared s3.amazonaws.com host, so the CSP allowlist only opens that one bucket rather than the whole generic S3 namespace. Assisted-by: ClaudeCode:claude-sonnet-5 Signed-off-by: Carl Schwan <carl@carlschwan.eu>
FavoritesLayer.vue rewritten in <script setup lang="ts"> against MapLibre GL instead of Leaflet + leaflet.markercluster: - A clustered GeoJSON source (native supercluster clustering) with a GL circle/count symbol layer for clusters and a symbol layer for individual favorites. - Marker look (colored circle + white star, "selected" highlight) is redrawn on an offscreen canvas and registered as a GL sprite image per distinct color, since GL symbol layers need raster icons rather than the CSS classes/div-icons Leaflet used. - Hover tooltip (name/category/comment) via a MapLibre Popup. - Clicking a favorite emits `click` (forwarded by Map.vue as `click-favorite`, unchanged from App.vue's perspective). - Clicking a cluster zooms to its expansion zoom. Not ported yet, on purpose: drag-to-edit, the right-click context menu (delete/copy-to-map on a marker, delete-multiple/zoom-to-bounds on a cluster), and spiderfying overlapping markers at max zoom. These need the broader per-marker interaction model (shared with tracks, devices, contacts, photos) redesigned for MapLibre, which is follow-up work. Assisted-by: ClaudeCode:claude-sonnet-5 Signed-off-by: Carl Schwan <carl@carlschwan.eu>
App.vue's onMapAddClick (used by the "add a favorite" flow) still reads the old Leaflet event shape (e.latlng.lat/lng). MapLibre's MapMouseEvent carries the position as e.lngLat instead, so add-click was firing with lat/lng undefined, throwing a TypeError from onMapAddClick before a favorite could ever be placed. Assisted-by: ClaudeCode:claude-sonnet-5 Signed-off-by: Carl Schwan <carl@carlschwan.eu>
vue-maplibre-gl's @Map:click doesn't forward MapLibre's native MapMouseEvent directly - it wraps it as { type, map, component, event }, with the actual event nested under .event. onMapClick was reading lngLat off the wrapper instead of the nested event, so add-click still fired with an undefined position. Also moved add-click's payload to App.vue's own event vocabulary instead of shimming the old Leaflet shape in Map.vue: it now emits a plain { lat, lng } position, and App.vue's onMapAddClick reads that directly rather than the old e.latlng.lat/lng. Assisted-by: ClaudeCode:claude-sonnet-5 Signed-off-by: Carl Schwan <carl@carlschwan.eu>
ContactsLayer.vue rewritten in <script setup lang="ts"> against MapLibre GL, following the same clustered-GeoJSON-source pattern as favorites: - Contact avatars are fetched and clipped to a circle on an offscreen canvas, then registered as GL sprite images (symbol layers need pre-registered raster icons, not arbitrary URLs); a neutral placeholder icon shows until an avatar finishes loading. - Clusters get a generic icon + count (not a representative avatar - GL clustering aggregates values, it can't pick "the first child's photo" the way the old code did). - Hover tooltip and a click popup (delete this address/location, copy to map, open in Contacts) via MapLibre Popups, matching the old markup/ classes closely enough to reuse the existing CSS. Not ported yet, on purpose: the separate right-click popup (same actions as the click popup, so nothing is actually lost) and spiderfying overlapping markers at max zoom. Fixes along the way: - ADRTYPE comes back from the backend as a plain string for single- address contacts but an array for multi-address ones; .map() on the string case threw inside a MapLibre-invoked hover/click handler and broke the map's event loop. Normalize it to an array first. - Map.vue never imported maplibre-gl's own CSS. Without it, .maplibregl-map/.maplibregl-ctrl/.maplibregl-popup have no positioning rules at all, so controls fell into normal document flow below the map and popups anchored to the viewport instead of their marker - this was latent since the very first step, just not obvious until a popup showed up. Also fixes favorites' hover tooltip positioning retroactively. - Extracted the HTML-escaping helper FavoritesLayer.vue already had into src/utils/domUtils.ts so ContactsLayer.vue can reuse it instead of duplicating it. Also switched this session's new/rewritten files' license headers to SPDX tags, keeping the original authors' copyright lines where the file predates this port. Assisted-by: ClaudeCode:claude-sonnet-5 Signed-off-by: Carl Schwan <carl@carlschwan.eu>
PhotosLayer.vue rewritten in <script setup lang="ts"> against MapLibre GL, following the same clustered-GeoJSON-source pattern as favorites and contacts: - Photo previews are fetched and drawn to a square thumbnail on an offscreen canvas, then registered as GL sprite images; a neutral placeholder shows until a preview finishes loading. - Clusters get a generic icon + count rather than a representative preview, same reasoning as contacts' avatar clusters. - Hover tooltip (preview/date/filename); clicking a photo opens it in Viewer; clicking a cluster either zooms in (mirroring the old "zoom before showing" behavior for clusters over 10 photos while not at max zoom) or opens every photo in that cluster in Viewer via getClusterLeaves(), falling back to open-sidebar on the first one if Viewer isn't available. - The date-range slider filter (dateFilterEnabled/Start/End) is ported as-is via the existing binSearch helper. Dropped on purpose, not deferred: the chunked cluster-loading progress events. MapLibre's clustering is synchronous and fast even for many thousands of points, so there's no "in progress" state left to report; App.vue's photosLoading is already managed around the actual data fetch elsewhere. Not ported yet, on purpose: the right-click popup (open sidebar, remove geo data, copy to map, spiderfy) and drag-to-edit. Assisted-by: ClaudeCode:claude-sonnet-5 Signed-off-by: Carl Schwan <carl@carlschwan.eu>
Use a GeoJSON clusterProperties reducer to carry one child point's iconId onto its cluster, and use that (falling back to the placeholder) as the cluster's icon-image instead of a fixed generic icon. supercluster aggregates bottom-up, so this isn't guaranteed to be the very first photo geographically/temporally, just some deterministic representative one - same caveat the old "first child marker" Leaflet code had, just for a different reason. Also added a text halo to the cluster count label, since it now renders over an arbitrary photo instead of a flat color and needs to stay legible either way. Assisted-by: ClaudeCode:claude-sonnet-5 Signed-off-by: Carl Schwan <carl@carlschwan.eu>
TracksLayer.vue rewritten in <script setup lang="ts"> against MapLibre GL. Unlike the marker-based layers (favorites/contacts/photos), tracks are line geometry, which maps directly onto MapLibre's native line layers rather than canvas-drawn sprite icons: - One GeoJSON source with LineString features (track segments/routes) and Point features (first point + waypoints), split by geometry-type filters into three layers: a black casing line, a colored line on top of it (matching the old two-polyline "casing" technique), and circle markers for the points (data-driven circle-color/circle-radius, no sprite generation needed here). - The time-window filtering logic (calculateLines/calculateFirstPoint/ calculateWaypoints) is carried over as-is - it was already framework- agnostic - fixing a transcription slip along the way where a segment's own (empty) name was kept instead of its parent track's name. - Hover tooltip on points (file/name/description/altitude/begin-time); clicking a line or a point emits `click` with the track. Dropped on purpose, not deferred: the point-hover event the old component emitted while hovering a line. Nothing has listened for it since the Leaflet-to-vue-maplibre-gl port started (confirmed via git history), so there's nothing left to preserve. Not ported yet, on purpose: the right-click popup (change color, display elevation, copy to map, download) and drag-to-edit. Assisted-by: ClaudeCode:claude-sonnet-5 Signed-off-by: Carl Schwan <carl@carlschwan.eu>
App.vue calls this.$refs.map.zoomOnTrack/zoomOnDevice/ zoomOnPhotoSuggestion/fitBounds/displayElevation directly from several places (track/device/photo-suggestion selection, "zoom to all favorites/contacts", etc.). The <script setup> rewrite of Map.vue dropped all of these, since script setup exposes nothing by default - every one of those call sites threw "is not a function". Restored them via defineExpose(), translating Leaflet-shaped bounds (App.vue still constructs L.latLngBounds(...) at most call sites) to MapLibre's [[west, south], [east, north]] format. displayElevation is a no-op for now since the elevation profile chart itself isn't ported yet. Also fixed an inverted, pre-existing condition in Sidebar.vue: the track metadata tab was gated behind `v-if="isPublic() && ..."`, but isPublic() means "no authenticated user" - so it only ever rendered for anonymous share viewers, never for a logged-in user clicking their own track. This was unreachable before TracksLayer was ported (nothing rendered tracks to click), which is why it went unnoticed until now. Assisted-by: ClaudeCode:claude-sonnet-5 Signed-off-by: Carl Schwan <carl@carlschwan.eu>
Sidebar.vue's custom tabs (favorite, photo-suggestion, maps-track- metadata) are all gated behind !fileInfo, since they're meant to replace the generic file sidebar rather than sit alongside it. onTrackClick was opening the sidebar with the track's real file path, which makes Sidebar.vue fetch that file's own fileInfo via a PROPFIND and populate it - which then hides the maps-track-metadata tab, leaving just the generic file header (a fresh, mostly-empty fileInfo: "0 B, seconds ago"). Open with no path instead, same as onFavoriteClick already does, so fileInfo stays null and the track metadata tab renders. Assisted-by: ClaudeCode:claude-sonnet-5 Signed-off-by: Carl Schwan <carl@carlschwan.eu>
DevicesLayer.vue rewritten in <script setup lang="ts"> against MapLibre GL, combining the two patterns already used elsewhere: a canvas-drawn sprite icon per device (colored circle + a simple desktop/phone glyph, matching the old thumbnail distinction) for the last known position, and native GL line layers (casing + colored line, same as tracks) for the history trail. - Hover tooltip (device name); clicking a marker or line emits `click` (forwarded by Map.vue as `click-device`, currently unconsumed by App.vue - same as before this port, nothing has regressed there). Dropped on purpose, not deferred: the >2500-points-per-device heatmap fallback (LHeatMap.vue, leaflet.heat) and its now fully-unused dependency. That existed to keep Leaflet's DOM-based rendering from choking on very long history trails; MapLibre renders line geometry on the GPU and doesn't need the same escape hatch. Also dropped the point-hover event this used to emit while hovering a marker or line, confirmed dead the same way as tracks' - nothing has listened for it since the Leaflet-to-vue-maplibre-gl port started. Not ported yet, on purpose: the right-click popup (toggle history, change color, export, link to map) and drag-to-edit. Assisted-by: ClaudeCode:claude-sonnet-5 Signed-off-by: Carl Schwan <carl@carlschwan.eu>
getDevice() requested limit=100000 points per page, but DevicesController::getDevicePoints() only accepts int<1, 10000> - Nextcloud's request validation rejects anything outside that range with a 400 before the controller body even runs. This has been broken since whichever server version started enforcing that constraint; device markers/history just never got tested through this exact path until now, since the map didn't render or interact with devices at all before this port. Use 10000 (the actual max) instead, keeping the same "keep paginating while a full page comes back" logic - it just takes more round trips for a device with a huge point history now. Assisted-by: ClaudeCode:claude-sonnet-5 Signed-off-by: Carl Schwan <carl@carlschwan.eu>
…directions RoutingMachine.vue is gone; RoutingControl.vue now drives a @maplibre/maplibre-gl-directions instance directly through useMap(), configured from the existing OSRM/Mapbox admin settings. The directions library's own click/drag waypoint interactivity is disabled - the existing step list (RoutingSteps/RoutingStep, unchanged) already provides full waypoint CRUD, and the two interaction models don't mix since the library has no concept of an empty waypoint slot. Not ported: the turn-by-turn instructions panel, since maplibre-gl-directions only draws the route and doesn't decode it into textual steps. GraphHopper routing is dropped rather than ported: maplibre-gl-directions only speaks OSRM/Mapbox-Directions-API request/response formats, and it was removed from the admin settings along with its CSP allowances and read/write endpoints. Toggled via a new button next to the street/satellite/terrain switch in Map.vue, since the search box that used to toggle it isn't ported yet. Assisted-by: ClaudeCode:claude-sonnet-5 Signed-off-by: Carl Schwan <carl@carlschwan.eu>
They were shown as soon as two waypoints had names, even with no router configured (or before a route had actually been fetched), so clicking them silently did nothing since there was never any route data to act on. Also restore the left margin that keeps the routing panel clear of the app's own nav toggle and zoom controls, lost in the rewrite. Assisted-by: ClaudeCode:claude-sonnet-5 Signed-off-by: Carl Schwan <carl@carlschwan.eu>
Rebuilds a localized turn-by-turn instruction list from the raw OSRM/
Mapbox route response (src/utils/routingInstructions.ts), ported from
leaflet-routing-machine's OSRMv1._convertRoute()/
Formatter.formatInstruction() logic since maplibre-gl-directions only
draws the route and doesn't decode it into text. Reuses the same
t('maps', ...) strings the old formatter used, so existing
translations keep working unchanged. Clicking an instruction flies
the map to its maneuver location.
Also swaps the routing panel's plain <button> elements for NcButton,
and fixes maplibre-gl's default .maplibregl-ctrl-group chrome (white
background, box-shadow, border-radius) clashing with the panel's own
styling - it's an ancestor of the panel's content so scoped :deep()
can't reach it; :has() targets it globally instead, scoped to just
this control via its distinguishing class.
Assisted-by: ClaudeCode:claude-sonnet-5
Signed-off-by: Carl Schwan <carl@carlschwan.eu>
setRouter() is called synchronously from an immediate watch(routers) at setup time, and calls syncWaypoints(), which read hasWaypoints and waypointCoords - both declared further down in the file. That's a temporal-dead-zone reference: it threw, aborting the rest of <script setup> before the watch(waypointCoords, syncWaypoints) further down ever got to run. Only the router <select>'s own @change handler (calling setRouter directly, well after setup) ever triggered a re-fetch as a result. Move waypointCoords/hasWaypoints/routeReady/latestDirections above everything that can call syncWaypoints() at setup time, so the dependency order is safe regardless of when things run. Assisted-by: ClaudeCode:claude-sonnet-5 Signed-off-by: Carl Schwan <carl@carlschwan.eu>
Adds a variant prop to RoutingStep (start/via/end) with a colored left border - green/default/red - and tightens the step list into a single bordered card with hairline separators instead of gapped rows. The router picker moves below the steps and becomes a pill-style button matching the app's primary color, instead of a plain <select> squeezed above them. Assisted-by: ClaudeCode:claude-sonnet-5 Signed-off-by: Carl Schwan <carl@carlschwan.eu>
…ents" This reverts commit 36bef4b. Signed-off-by: Carl Schwan <carl@carlschwan.eu>
SearchControl.vue is now a thin MglCustomControl wrapping the
existing SearchField (unchanged - it only ever produced plain
{lat,lng}-shaped results, never depended on Leaflet being the map
engine). Map.vue owns the result state and decides what a selected
result means, same split as before:
- contact/favorite: fly to it
- device/track: zoom if enabled, otherwise ask App.vue to enable it
- poi: bounded Nominatim category search, rendered by the new
PoiMarkersLayer.vue
- result: a single already-fetched Nominatim result, same layer
- coordinate: shows the new ClickSearchMarker.vue with a reverse-
geocoded address
- mylocation: geolocate and fly there
PoiMarkersLayer.vue (ported from PoiMarker.vue) draws all results
with one shared generic pin icon rather than Nominatim's per-category
icon images - avoids a per-poi image fetch and CSP allowance for a
cosmetic difference. Its unused opening-hours computed properties
(dead code - never referenced by the old template) were dropped.
Not ported on either new marker component: add-contact-address - it
depended on a "place an existing contact by address" mode that isn't
implemented yet in the new architecture. Only add-to-favorites
survived.
The search box occupies the same top-left slot as RoutingControl -
only one is shown at a time, same as before.
Assisted-by: ClaudeCode:claude-sonnet-5
Signed-off-by: Carl Schwan <carl@carlschwan.eu>
Right-click a favorite for delete/copy-to-map (matching the old per-marker menu), or a cluster for delete-favorites/zoom-to-bounds (via getClusterLeaves/getClusterExpansionZoom - MapLibre clustering has no spiderfy equivalent, so that part of the old cluster menu stays dropped, noted in the header comment). Drag-to-edit is plain pointer-event tracking (mousedown/mousemove/ mouseup) since GL symbol layers aren't real DOM elements and can't use native HTML5 drag: a local draggingOverride ref feeds the dragged point's live position into the GeoJSON source so it follows the cursor without a round-trip through the parent on every move, and only commits (emits edit) on mouseup. Map panning is disabled for the duration of the drag. Wired through Map.vue with the same event names App.vue's template already listens for (edit-favorite/delete-favorite/delete-favorites/ add-to-map-favorite/favorites-draggable), so no App.vue changes were needed. This is the first of the deferred per-layer right-click/drag-to-edit batches; contacts/photos/tracks/devices follow the same pattern. Assisted-by: ClaudeCode:claude-sonnet-5 Signed-off-by: Carl Schwan <carl@carlschwan.eu>
geocode/searchAddress/searchPois all called nominatim via plain axios,
which uses XHR and can't override the page's Referrer-Policy per
request. If that policy is stricter than 'origin' (Nextcloud's own
default can be), these requests go out with no referer at all, and
Nominatim's usage policy treats unidentified requests as abusive and
blocks or rate-limits them - the same issue the tile layers already
had, fixed on another branch via their referrerPolicy option.
Switched these three to a plain fetch() with referrerPolicy: 'origin'
set explicitly, wrapping the result as { data } to keep every call
site's existing response.data access working unchanged.
Assisted-by: ClaudeCode:claude-sonnet-5
Signed-off-by: Carl Schwan <carl@carlschwan.eu>
The immediate watch(routers) that auto-selects a router at setup time wasn't gated on the map's style being loaded, only on the Map instance existing. new MapLibreGlDirections() calls map.addSource() synchronously in its constructor, so if the style hadn't finished loading yet - routine at startup, since routers can become available before the map does - it threw 'Style is not done loading', an uncaught exception during Map.vue's render that could plausibly disrupt other reactive updates queued in the same flush (e.g. a just-arrived POI search result). Assisted-by: ClaudeCode:claude-sonnet-5 Signed-off-by: Carl Schwan <carl@carlschwan.eu>
onSearchValidate's poi branch had no .catch(), so a rejected request (network error, bad response) failed with nothing visible - clicking a poi category looked like it did nothing at all. Now shows an error toast on failure, and one for a genuinely empty result set too, so 'no results in view' isn't indistinguishable from 'broken'. Assisted-by: ClaudeCode:claude-sonnet-5 Signed-off-by: Carl Schwan <carl@carlschwan.eu>
The icon was drawn off-center (pin-style, at 40% height) with icon-anchor: bottom, unlike every other layer's marker icon (all plain centered circles with the default center anchor). Hover/click weren't registering on the rendered icon at all even though it drew correctly - a mismatch between the visible icon and the anchor used for hit-testing is the most likely explanation. Matches the same centered-circle pattern FavoritesLayer/DevicesLayer/ContactsLayer already use successfully. Assisted-by: ClaudeCode:claude-sonnet-5 Signed-off-by: Carl Schwan <carl@carlschwan.eu>
MglSymbolLayer had no :filter, unlike every other layer's symbol layer in this app. With it omitted, the layer and its geojson source both existed correctly (confirmed via getLayer/getSource), the icon was registered (confirmed via hasImage), and the layout was correct (confirmed via getLayoutProperty) - but queryRenderedFeatures for the layer always came back empty: nothing was actually being placed. Adding an explicit filter, matching the pattern every other layer already used, fixed it. Also, following MapLibre's own "Display a popup on click" example (https://maplibre.org/maplibre-gl-js/docs/examples/display-a-popup-on-click/), each feature now carries a per-result icon name and icon-image reads it via ['get', 'icon']. OpenFreeMap Liberty's own sprite already ships icons for most amenity categories (restaurant, cafe, fuel, library, pharmacy, ...) under the same OSM tag names Nominatim returns as `type`, so those are reused directly instead of drawing one generic marker for everything; a teardrop pin (anchored at its point, like a normal map pin) is drawn only as a fallback for categories the style doesn't have an icon for. Assisted-by: ClaudeCode:claude-sonnet-5 Signed-off-by: Carl Schwan <carl@carlschwan.eu>
OpenFreeMap Liberty renders its own POI icons (restaurants, shops, etc.) directly from the vector tiles - those were purely decorative before, unlike our own search-result markers. Clicking one now shows the same kind of popup (name + Add to favorites), using the base style's poi vector layers (source-layer 'poi', the OpenMapTiles schema) rather than anything this app added itself. The poi layer IDs are looked up by source-layer at click/hover time rather than hardcoded (they're rank/zoom-threshold splits specific to the current style version, e.g. poi_r7/poi_r20) - this also means the lookup stays correct across street/satellite style switches without needing to re-bind listeners. Assisted-by: ClaudeCode:claude-sonnet-5 Signed-off-by: Carl Schwan <carl@carlschwan.eu>
icon-size 1.5 instead of 1 so search results stand out from the base style's own (unscaled) poi icons at the same zoom level. Also bumped the fallback pin's canvas resolution so it stays reasonably crisp at the larger size. Assisted-by: ClaudeCode:claude-sonnet-5 Signed-off-by: Carl Schwan <carl@carlschwan.eu>
Assisted-by: ClaudeCode:claude-sonnet-5 Signed-off-by: Carl Schwan <carl@carlschwan.eu>
- After picking a suggestion, the typed text stayed in the input on top of the selected value, and a focus handler copied the value into the input again, so the text was drawn twice. - The separate "Clear POIs" button duplicated the field's own clear button. Clearing the field now removes the search results and the searched point from the map instead. - MapLibre's control frame drew an outline around the field, the existing override was less specific than MapLibre's rule. - Align the field with the navigation toggle: same top offset and height, starting one gap after the toggle, also on mobile where it could slide under it. Assisted-by: ClaudeCode:claude-opus-5-5 Signed-off-by: Carl Schwan <carl@carlschwan.eu>
…URLs - Without a saved travel mode, selectedRouter is an empty string, which the ?? fallback kept instead of falling back to the first router, so no routing engine was set up and no route was ever requested. - Routing URLs are trimmed when saved, and the stored values when building the CSP and the routers: a leading space made parse_url() miss the host, so the routing server was not allowed by the CSP. A trailing slash is stripped as the profile is appended to the URL. Assisted-by: ClaudeCode:claude-opus-5-5 Signed-off-by: Carl Schwan <carl@carlschwan.eu>
Bring back the "Find directions" button next to the search field, the way into routing on master that the port dropped. The routing panel is redesigned: - A card next to the navigation toggle, aligned with the search field; the hidden one of the two no longer pushes the other down. - A labelled travel mode select with icons instead of emoji, and a warning card when no routing service is configured. - The stops as a timeline with start, via and destination markers, reverse and "Add stop" buttons; clearing a stop's field empties it. - A summary with duration and distance, zoom to route, and an export menu replacing the "Export as" dialog. - The map fits a new route next to the panel, and long turn-by-turn instructions wrap inside their row. Long search suggestions are shown on one line: the option styles were nested under the field, but the dropdown is rendered in the body. Assisted-by: ClaudeCode:claude-opus-5-5 Signed-off-by: Carl Schwan <carl@carlschwan.eu>
CarlSchwan
force-pushed
the
port-to-maplibre-gl
branch
from
October 15, 2026 10:19
29d4f11 to
34e8383
Compare
OCA.Sharing.ExternalShareActions is deprecated and the action was no longer offered in the files sidebar. Register it as an inline action of the share link with registerSidebarInlineAction() from @nextcloud/sharing/ui instead, updating @nextcloud/sharing to 0.4.0. It is now only offered for link shares of folders, as only a folder can be a map. The toast it shows used the dialogs library without its stylesheet, so the stylesheet is imported and loaded with the script. Assisted-by: ClaudeCode:claude-opus-5-5 Signed-off-by: Carl Schwan <carl@carlschwan.eu>
PublicUtilsController read the routing settings through $this->appConfig, which AddCspTrait declared private, so the property was undefined in the subclass and getOptionsValues() failed, leaving the public map page blank. Assisted-by: ClaudeCode:claude-opus-5-5 Signed-off-by: Carl Schwan <carl@carlschwan.eu>
getToken() read the hidden sharingToken input, which mounting the app replaces, so photos and favorites loaded during setup used the logged-in endpoints: anonymous visitors got nothing and logged-in visitors saw their own data. Fall back to the token in the URL. The photo scan status is the viewer's own, so it is no longer requested on public pages. Assisted-by: ClaudeCode:claude-opus-5-5 Signed-off-by: Carl Schwan <carl@carlschwan.eu>
- The settings are the visitor's own (tracking their position, registering geo links) or would save into the shared map's options, so they are no longer offered on public map pages. - Position tracking was started when the shared map's options had it enabled, posting the visitor's position to the logged-in devices endpoint. - The public page footer covered the bottom of the navigation, core only leaves room for it in the app content. Assisted-by: ClaudeCode:claude-opus-5-5
- Choosing "Rename" in the map actions closed the switcher panel, as the menu is rendered outside of it, so the rename field never showed. Rename with an inline input in the actions menu instead. - The rename response is the map's settings, not the map, and the folder is moved, so the renamed entry lost its id, path and current state. Reload the map list after renaming. - Deleting the open map left it open, now the default map is opened. - A new map is opened right after creating it. Assisted-by: ClaudeCode:claude-opus-5-5
Use the non-legacy settings dialog with form boxes, like the files app: switches with descriptions, the geo link registration as a form box button and the map projection note as a link. Assisted-by: ClaudeCode:claude-opus-5-5
Popup.on() returns a subscription since MapLibre 5, so chaining addTo() after it threw and no popup opened. Assisted-by: ClaudeCode:claude-opus-5-5 Signed-off-by: Carl Schwan <carl@carlschwan.eu>
Clicking a favorite on a public favorites share opens it in a sidebar, editable when the share allows edits, instead of a popup. Selecting a favorite no longer zooms the map out. Assisted-by: ClaudeCode:claude-opus-5-5 Signed-off-by: Carl Schwan <carl@carlschwan.eu>
Assisted-by: ClaudeCode:claude-opus-5-5 Signed-off-by: Carl Schwan <carl@carlschwan.eu>
Assisted-by: ClaudeCode:claude-opus-5-5 Signed-off-by: Carl Schwan <carl@carlschwan.eu>
Drop the commented-out Leaflet track code, the unused myMapsEnabled option, the "Say hi" placeholder action and styles for the old multiselect component. Assisted-by: ClaudeCode:claude-opus-5-5 Signed-off-by: Carl Schwan <carl@carlschwan.eu>
Signed-off-by: Carl Schwan <carl@carlschwan.eu>
Signed-off-by: Carl Schwan <carl@carlschwan.eu>
Signed-off-by: Carl Schwan <carl@carlschwan.eu>
Signed-off-by: Carl Schwan <carl@carlschwan.eu>
CarlSchwan
force-pushed
the
port-to-maplibre-gl
branch
2 times, most recently
from
October 16, 2026 10:51
9e2cf31 to
9dd4775
Compare
CarlSchwan
marked this pull request as ready for review
October 16, 2026 10:52
CarlSchwan
force-pushed
the
port-to-maplibre-gl
branch
2 times, most recently
from
October 16, 2026 10:56
c3b04ab to
5f6db2a
Compare
IRootFolder::getUserFolder() returns an IUserFolder since Nextcloud 36, which does not exist on Nextcloud 35. Assisted-by: ClaudeCode:claude-opus-5-5 Signed-off-by: Carl Schwan <carl@carlschwan.eu>
CarlSchwan
force-pushed
the
port-to-maplibre-gl
branch
from
October 16, 2026 11:10
5f6db2a to
e85a2ca
Compare
Signed-off-by: Carl Schwan <carl@carlschwan.eu>
CarlSchwan
force-pushed
the
port-to-maplibre-gl
branch
from
October 16, 2026 11:20
e85a2ca to
5d95e55
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
🤖 AI (if applicable)