Jump to today, map-app links, drag to day, PDF landing time (#1891)

* Open a running trip on today

Asked for in discussion #1567: during a trip, opening the planner starts
at day 1 and you scroll to find out what is planned for today.

The mobile shell has done this since the plan tab was built - it seeds
the active day with today when the trip is running. The desktop day plan
never did, so the same trip behaved differently depending on the screen
you opened it on. It now selects today's day, expands it and scrolls it
into view, once per mount and only while nothing is selected yet, so a
deep link or a day picked before switching tabs still wins.

The date comparison moved into one helper both sides use. It is
deliberately local rather than toISOString(): 08:00 in Tokyo is still
yesterday in UTC, and a trip day is a calendar day where the traveller
is standing.

Also fixes a note losing its colour when dragged to another day. A move
is a delete plus a create, and the create was still sending the three
fields notes had before colours existed.

* Open a collection place in a map app

Asked for in discussion #1455: a saved place is somewhere you intend to
go, but the only way to get directions to one was to add it to a trip
first, where the button has been for a while.

The collection detail gets the same picker off the same helper: Google
Maps, Waze, Apple Maps and OpenStreetMap, filtered by what the place can
actually be located with. A place with coordinates offers all of them; a
place typed in by hand with just a name and an address still offers the
two that take a search string, and a place with neither shows no button
at all.

It sits on the meta line beside the address, quieter than the link chips
below it, because it is an action on the place rather than a piece of it.

* Drag a place off the map onto a day

Asked for in discussion #891: assigning a place means finding it again in
the sidebar, even though it is right there under the cursor on the map.

The drop half already existed and did not need touching. Every day, every
row and every gap between rows in the day plan reads a placeId and calls
onAssignToDay with the position it was dropped at, complete with the
toast and the undo entry. What was missing was a second place to pick a
place UP from - the only one was the row in the places sidebar.

Markers become that second source. Both renderers build their markers as
real DOM nodes, so one helper covers Leaflet and the GL variants alike,
and it sets the same two channels the sidebar row does: dataTransfer for
the drop, and window.__dragData because browsers hide dataTransfer while
the pointer is merely hovering. The day plan cannot tell the two apart.

Not offered on touch: HTML5 drag is a pointer feature, and on a phone
the day plan is not on screen next to the map anyway.

* Show the landing time in the PDF

Asked for in discussion #1310: the export prints a flight's departure and
nothing else, so a reader cannot tell how much of the day is left for
anything after it.

The day plan has drawn both times as "14:30 - 16:45" for a while; the
export never got the rule. Now it does, for any transport that begins and
ends on the same day - a train and a ferry read the same way a flight
does.

Across a span it stays as it was: the arrival belongs to the arrival day,
which already prints it as its own time, and repeating it on the
departure day would put tomorrow's clock next to today's departure.
This commit is contained in:
Maurice
2026-08-11 23:26:58 +02:00
committed by GitHub
parent 92b9e7548e
commit 62bc7ff8fd
17 changed files with 551 additions and 13 deletions
+18 -2
View File
@@ -3,6 +3,7 @@ import DOM from 'react-dom'
import { renderIconMarkup } from '../../utils/iconMarkup'
import { MapContainer, TileLayer, Marker, Polyline, CircleMarker, Circle, useMap, Tooltip } from 'react-leaflet'
import MarkerClusterGroup from 'react-leaflet-cluster'
import { makeMarkerDraggable } from './markerDrag'
import L from 'leaflet'
import 'leaflet.markercluster/dist/MarkerCluster.css'
import 'leaflet.markercluster/dist/MarkerCluster.Default.css'
@@ -460,17 +461,28 @@ interface MemoMarkerProps {
onClickPlace: (id: number) => void
onHover: (place: any, x: number, y: number) => void
onHoverOut: () => void
/** Off in read-only trips and on the phone, where HTML5 drag does not exist. */
draggable: boolean
}
const MemoMarker = memo(function MemoMarker({
place, isSelected, orderNumbers, photoUrl, onClickPlace, onHover, onHoverOut,
place, isSelected, orderNumbers, photoUrl, onClickPlace, onHover, onHoverOut, draggable,
}: MemoMarkerProps) {
const icon = createPlaceIcon({ ...place, image_url: photoUrl }, orderNumbers, isSelected)
const cleanupRef = useRef<(() => void) | null>(null)
return (
<Marker
position={[place.lat, place.lng]}
icon={icon}
eventHandlers={{
// The element only exists once Leaflet has put the marker on the map,
// and it is rebuilt whenever the icon changes (selection, day number),
// so the wiring is redone on every add rather than once on mount.
add: (e: any) => {
cleanupRef.current?.()
cleanupRef.current = draggable ? makeMarkerDraggable(e.target.getElement() as HTMLElement, place.id) : null
},
remove: () => { cleanupRef.current?.(); cleanupRef.current = null },
click: () => onClickPlace(place.id),
mouseover: (e: any) => onHover(place, e.originalEvent.clientX, e.originalEvent.clientY),
mousemove: (e: any) => onHover(place, e.originalEvent.clientX, e.originalEvent.clientY),
@@ -681,6 +693,9 @@ export const MapView = memo(function MapView({
}, [])
const isTouchDevice = typeof window !== 'undefined' && navigator.maxTouchPoints > 0
// Drag a marker onto a day (#891). Pointer-driven, so it is off wherever
// HTML5 drag does not exist — and the day plan is not on screen there anyway.
const markersDraggable = !isTouchDevice
const markers = useMemo(() => places.map((place) => {
const isSelected = place.id === selectedPlaceId
@@ -698,9 +713,10 @@ export const MapView = memo(function MapView({
onClickPlace={handleMarkerClick}
onHover={handleMarkerHover}
onHoverOut={handleMarkerHoverOut}
draggable={markersDraggable}
/>
)
}), [places, selectedPlaceId, dayOrderMap, photoUrls, handleMarkerClick, handleMarkerHover, handleMarkerHoverOut])
}), [places, selectedPlaceId, dayOrderMap, photoUrls, handleMarkerClick, handleMarkerHover, handleMarkerHoverOut, markersDraggable])
// Parsing track geometry is the expensive part (tracks run to tens of thousands
// of points), so it hangs off `places` alone — a selection change must not