mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-19 21:46:49 +00:00
0dd07befa7
The Plan tab was the last trip tab still in the old design. It now reads like Bookings, Costs, Lists, Files and Collab, without losing a function: - Day plan: each day is a card with a head band (day tile with weather, stay and rental pills, a "+" menu for place, stay, transport, transit and note), rows with "..." menus, white badges and a white route bar. - Places column: add, search and filters in three rows, a show dropdown, category and rating filters, and a selection bar at the foot. - Place inspector, day details and the stay editor on the shared dialog frame; the place form in the editor look with the name in the head band. - Clicking a booking or transport anywhere on the plan opens the booking detail popup first; Edit leads to the editor. The popup is one host shared with the Bookings and Transports tabs. - Files and Collab dialogs, notes, polls, links and chat moved to the same frame; shared DialogShell and dialogParts hold the building blocks. - The map hover card shows the place photo and follows the theme. Every new and reworded string is translated into all locales, and a few older English leftovers (help center, cost split, import hints) with them. Phone screens keep their own look.
90 lines
4.0 KiB
TypeScript
90 lines
4.0 KiB
TypeScript
import { escapeHtml } from '@trek/shared'
|
|
import { getCached } from '../../services/photoService'
|
|
|
|
const PHOTO_PROXY_PREFIX = '/api/maps/place-photo/'
|
|
|
|
/** The fields of a place that decide which picture its marker gets. */
|
|
interface PhotoSource {
|
|
image_url?: string | null
|
|
google_place_id?: string | null
|
|
osm_id?: string | null
|
|
lat?: number | null
|
|
lng?: number | null
|
|
}
|
|
|
|
/**
|
|
* A place's image_url that is directly displayable on a map marker and should
|
|
* win over the auto-fetched provider thumbnail — a custom uploaded image (#1136)
|
|
* or an inline data URL. Provider proxy URLs (/api/maps/place-photo/…) are
|
|
* deliberately excluded: those still go through the downscale/thumb path so many
|
|
* markers don't lag on zoom.
|
|
*/
|
|
export function isCustomPlaceImage(url: string | null | undefined): boolean {
|
|
return !!url && (url.startsWith('/uploads/') || url.startsWith('data:'))
|
|
}
|
|
|
|
/**
|
|
* Cache key under which a place's auto-fetched thumbnail is stored: the provider
|
|
* photo the user picked when there is one, else the provider id, otherwise the
|
|
* coordinates. The picked photo comes first because picking another picture of the
|
|
* same place leaves its provider id alone, and the thumb cached under that id
|
|
* would go on answering for the new one. A place with none of these has no
|
|
* identity to cache under, so it gets the empty string: callers skip it rather
|
|
* than filing every coordinate-less place under one shared entry.
|
|
*/
|
|
export function photoCacheKey(place: PhotoSource): string {
|
|
if (place.image_url?.startsWith(PHOTO_PROXY_PREFIX)) return place.image_url
|
|
if (place.google_place_id) return place.google_place_id
|
|
if (place.osm_id) return place.osm_id
|
|
if (place.lat == null || place.lng == null) return ''
|
|
return `${place.lat},${place.lng}`
|
|
}
|
|
|
|
/**
|
|
* What the maps' thumb loading depends on, as one string to key the effect on.
|
|
* Place ids alone missed every change of picture: taking an upload off a place or
|
|
* picking another suggested photo changes no id, so the marker kept a category
|
|
* dot or the old thumb until the page was reloaded.
|
|
*/
|
|
export function photoSourcesKey(places: Array<PhotoSource & { id: number }>): string {
|
|
return places.map(p => `${p.id}:${isCustomPlaceImage(p.image_url) ? 'own' : photoCacheKey(p)}`).join('|')
|
|
}
|
|
|
|
/**
|
|
* The picture a place wears on the map: a custom uploaded image wins over the
|
|
* auto-fetched thumb, which wins over whatever image_url says. The marker and the
|
|
* hover card both ask this, so they never show two different pictures.
|
|
*/
|
|
export function placePhotoUrl(place: PhotoSource, thumbs: Record<string, string>): string | null {
|
|
if (isCustomPlaceImage(place.image_url)) return place.image_url!
|
|
const key = photoCacheKey(place)
|
|
return (key && thumbs[key]) || place.image_url || null
|
|
}
|
|
|
|
/**
|
|
* The same picture at full size, for the hover card. The marker's thumb is a 48px
|
|
* circle cut for a round pin, which blurs and keeps its round cut-out when it is
|
|
* shown any larger, so the card asks the photo cache for the picture it came from.
|
|
*/
|
|
export function placePhotoFull(place: PhotoSource): string | null {
|
|
if (isCustomPlaceImage(place.image_url)) return place.image_url!
|
|
const key = photoCacheKey(place)
|
|
const full = key ? getCached(key)?.photoUrl : null
|
|
if (full) return full
|
|
return place.image_url?.startsWith(PHOTO_PROXY_PREFIX) ? place.image_url : null
|
|
}
|
|
|
|
/**
|
|
* The picture inside a round place marker, for both the Leaflet and the GL map.
|
|
*
|
|
* Sized by its style and not by width/height attributes, because stylesheet rules
|
|
* beat attributes: Tailwind's preflight gives every img `height: auto`, and
|
|
* Leaflet's marker pane adds `width: auto`. The 48px square thumbs got away with
|
|
* that. An uploaded photo kept its own proportions on the GL map and its full pixel
|
|
* size on Leaflet, where only the rounded-off corner of a 4000px image lands inside
|
|
* the circle, so the marker showed nothing but its category colour.
|
|
*/
|
|
export function markerPhotoHtml(url: string): string {
|
|
return `<img src="${escapeHtml(url)}" alt="" style="display:block;width:100%;height:100%;border-radius:50%;object-fit:cover;" />`
|
|
}
|