mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-18 21:16:47 +00:00
feat(roadtrip): remember which track a day follows
The vias already made the day follow the road; nothing said which road. After a reload the badge could only say "shaped by hand", and re-fitting after the stops changed meant finding the track again by eye. One row per day, written in the same transaction as the chain it describes: a day claiming to follow a road whose vias never landed would be worse than a day claiming nothing. Three states rather than two, which is why the field is an object and not an id — absent leaves whatever the day followed alone, null says it follows nothing, an object records the road it now takes. A bare nullable id could not tell "do not touch this" apart from "forget it", and a hand-dragged via on the same day must not wipe the label. The foreign key is the point: delete the imported track and the label cascades with it, so no day is left claiming to follow a line nobody can see. The vias stay, because they are the drive. Both write paths check that the place is a track of this trip before it becomes a label — permission alone would let another trip's place id, or an ordinary place, become a name that can never be drawn. Also clears three reliability findings the scanner raised on this branch: two default sorts given an explicit comparator, and a trailing-slash regex the scanner is right to call backtracking, walked instead.
This commit is contained in:
@@ -64,6 +64,7 @@ const roadtrip: TranslationStrings = {
|
||||
'roadtrip.track.errorRoute': 'لم تستجب خدمة تحديد المسارات.',
|
||||
'roadtrip.track.errorSave': 'تعذّر حفظ نقاط العبور.',
|
||||
'roadtrip.track.clear': 'إزالة {count} نقطة عبور',
|
||||
'roadtrip.track.current': 'يتبع حالياً {name}',
|
||||
'roadtrip.poi.rest': 'استراحة',
|
||||
'roadtrip.poi.campsite': 'مخيم',
|
||||
'roadtrip.poi.food': 'طعام',
|
||||
|
||||
@@ -64,6 +64,7 @@ const roadtrip: TranslationStrings = {
|
||||
'roadtrip.track.errorRoute': 'O serviço de rotas não respondeu.',
|
||||
'roadtrip.track.errorSave': 'Não foi possível salvar os pontos de passagem.',
|
||||
'roadtrip.track.clear': 'Remover {count} pontos de passagem',
|
||||
'roadtrip.track.current': 'Atualmente segue {name}',
|
||||
'roadtrip.poi.rest': 'Área de descanso',
|
||||
'roadtrip.poi.campsite': 'Camping',
|
||||
'roadtrip.poi.food': 'Comida',
|
||||
|
||||
@@ -64,6 +64,7 @@ const roadtrip: TranslationStrings = {
|
||||
'roadtrip.track.errorRoute': 'El servei de rutes no ha respost.',
|
||||
'roadtrip.track.errorSave': 'No s’han pogut desar els punts de pas.',
|
||||
'roadtrip.track.clear': 'Elimina {count} punts de pas',
|
||||
'roadtrip.track.current': 'Actualment segueix {name}',
|
||||
'roadtrip.poi.rest': 'Àrea de descans',
|
||||
'roadtrip.poi.campsite': 'Càmping',
|
||||
'roadtrip.poi.food': 'Menjar',
|
||||
|
||||
@@ -64,6 +64,7 @@ const roadtrip: TranslationStrings = {
|
||||
'roadtrip.track.errorRoute': 'Služba plánování trasy neodpověděla.',
|
||||
'roadtrip.track.errorSave': 'Průjezdní body se nepodařilo uložit.',
|
||||
'roadtrip.track.clear': 'Odebrat {count} průjezdních bodů',
|
||||
'roadtrip.track.current': 'Aktuálně sleduje {name}',
|
||||
'roadtrip.poi.rest': 'Odpočívadlo',
|
||||
'roadtrip.poi.campsite': 'Kemp',
|
||||
'roadtrip.poi.food': 'Jídlo',
|
||||
|
||||
@@ -64,6 +64,7 @@ const roadtrip: TranslationStrings = {
|
||||
'roadtrip.track.errorRoute': 'Der Routendienst hat nicht geantwortet.',
|
||||
'roadtrip.track.errorSave': 'Die Zwischenpunkte konnten nicht gespeichert werden.',
|
||||
'roadtrip.track.clear': '{count} Zwischenpunkte entfernen',
|
||||
'roadtrip.track.current': 'Folgt derzeit {name}',
|
||||
'roadtrip.poi.rest': 'Rastplatz',
|
||||
'roadtrip.poi.campsite': 'Campingplatz',
|
||||
'roadtrip.poi.food': 'Essen',
|
||||
|
||||
@@ -64,6 +64,7 @@ const roadtrip: TranslationStrings = {
|
||||
'roadtrip.track.errorRoute': 'The routing service did not answer.',
|
||||
'roadtrip.track.errorSave': 'The via points could not be saved.',
|
||||
'roadtrip.track.clear': 'Drop {count} via points',
|
||||
'roadtrip.track.current': 'Currently following {name}',
|
||||
'roadtrip.poi.rest': 'Rest area',
|
||||
'roadtrip.poi.campsite': 'Campsite',
|
||||
'roadtrip.poi.food': 'Food',
|
||||
|
||||
@@ -64,6 +64,7 @@ const roadtrip: TranslationStrings = {
|
||||
'roadtrip.track.errorRoute': 'El servicio de rutas no respondió.',
|
||||
'roadtrip.track.errorSave': 'No se pudieron guardar los puntos de paso.',
|
||||
'roadtrip.track.clear': 'Quitar {count} puntos de paso',
|
||||
'roadtrip.track.current': 'Actualmente sigue {name}',
|
||||
'roadtrip.poi.rest': 'Área de descanso',
|
||||
'roadtrip.poi.campsite': 'Camping',
|
||||
'roadtrip.poi.food': 'Comida',
|
||||
|
||||
@@ -64,6 +64,7 @@ const roadtrip: TranslationStrings = {
|
||||
'roadtrip.track.errorRoute': 'Le service de calcul d’itinéraire n’a pas répondu.',
|
||||
'roadtrip.track.errorSave': 'Les points de passage n’ont pas pu être enregistrés.',
|
||||
'roadtrip.track.clear': 'Supprimer {count} points de passage',
|
||||
'roadtrip.track.current': 'Suit actuellement {name}',
|
||||
'roadtrip.poi.rest': 'Aire de repos',
|
||||
'roadtrip.poi.campsite': 'Camping',
|
||||
'roadtrip.poi.food': 'Restauration',
|
||||
|
||||
@@ -64,6 +64,7 @@ const roadtrip: TranslationStrings = {
|
||||
'roadtrip.track.errorRoute': 'Η υπηρεσία δρομολόγησης δεν απάντησε.',
|
||||
'roadtrip.track.errorSave': 'Δεν ήταν δυνατή η αποθήκευση των ενδιάμεσων σημείων.',
|
||||
'roadtrip.track.clear': 'Αφαίρεση {count} ενδιάμεσων σημείων',
|
||||
'roadtrip.track.current': 'Ακολουθεί αυτή τη στιγμή {name}',
|
||||
'roadtrip.poi.rest': 'Χώρος ανάπαυσης',
|
||||
'roadtrip.poi.campsite': 'Κάμπινγκ',
|
||||
'roadtrip.poi.food': 'Φαγητό',
|
||||
|
||||
@@ -64,6 +64,7 @@ const roadtrip: TranslationStrings = {
|
||||
'roadtrip.track.errorRoute': 'Az útvonaltervező szolgáltatás nem válaszolt.',
|
||||
'roadtrip.track.errorSave': 'A köztes pontokat nem sikerült menteni.',
|
||||
'roadtrip.track.clear': '{count} köztes pont eltávolítása',
|
||||
'roadtrip.track.current': 'Jelenleg ezt követi: {name}',
|
||||
'roadtrip.poi.rest': 'Pihenőhely',
|
||||
'roadtrip.poi.campsite': 'Kemping',
|
||||
'roadtrip.poi.food': 'Étkezés',
|
||||
|
||||
@@ -64,6 +64,7 @@ const roadtrip: TranslationStrings = {
|
||||
'roadtrip.track.errorRoute': 'Layanan rute tidak menjawab.',
|
||||
'roadtrip.track.errorSave': 'Titik lintas tidak dapat disimpan.',
|
||||
'roadtrip.track.clear': 'Hapus {count} titik lintas',
|
||||
'roadtrip.track.current': 'Saat ini mengikuti {name}',
|
||||
'roadtrip.poi.rest': 'Tempat istirahat',
|
||||
'roadtrip.poi.campsite': 'Perkemahan',
|
||||
'roadtrip.poi.food': 'Makanan',
|
||||
|
||||
@@ -64,6 +64,7 @@ const roadtrip: TranslationStrings = {
|
||||
'roadtrip.track.errorRoute': 'Il servizio di calcolo percorso non ha risposto.',
|
||||
'roadtrip.track.errorSave': 'Non è stato possibile salvare i punti di passaggio.',
|
||||
'roadtrip.track.clear': 'Rimuovi {count} punti di passaggio',
|
||||
'roadtrip.track.current': 'Attualmente segue {name}',
|
||||
'roadtrip.poi.rest': 'Area di sosta',
|
||||
'roadtrip.poi.campsite': 'Campeggio',
|
||||
'roadtrip.poi.food': 'Ristoro',
|
||||
|
||||
@@ -64,6 +64,7 @@ const roadtrip: TranslationStrings = {
|
||||
'roadtrip.track.errorRoute': 'ルート検索サービスが応答しませんでした。',
|
||||
'roadtrip.track.errorSave': '経由地を保存できませんでした。',
|
||||
'roadtrip.track.clear': '経由地 {count} か所を削除',
|
||||
'roadtrip.track.current': '現在 {name} をたどっています',
|
||||
'roadtrip.poi.rest': 'サービスエリア',
|
||||
'roadtrip.poi.campsite': 'キャンプ場',
|
||||
'roadtrip.poi.food': '食事',
|
||||
|
||||
@@ -64,6 +64,7 @@ const roadtrip: TranslationStrings = {
|
||||
'roadtrip.track.errorRoute': '경로 서비스가 응답하지 않았습니다.',
|
||||
'roadtrip.track.errorSave': '경유지를 저장하지 못했습니다.',
|
||||
'roadtrip.track.clear': '경유지 {count}개 삭제',
|
||||
'roadtrip.track.current': '현재 {name}을(를) 따라갑니다',
|
||||
'roadtrip.poi.rest': '휴게소',
|
||||
'roadtrip.poi.campsite': '캠핑장',
|
||||
'roadtrip.poi.food': '식사',
|
||||
|
||||
@@ -64,6 +64,7 @@ const roadtrip: TranslationStrings = {
|
||||
'roadtrip.track.errorRoute': 'De routeservice antwoordde niet.',
|
||||
'roadtrip.track.errorSave': 'De tussenpunten konden niet worden opgeslagen.',
|
||||
'roadtrip.track.clear': '{count} tussenpunten verwijderen',
|
||||
'roadtrip.track.current': 'Volgt momenteel {name}',
|
||||
'roadtrip.poi.rest': 'Rustplaats',
|
||||
'roadtrip.poi.campsite': 'Camping',
|
||||
'roadtrip.poi.food': 'Eten',
|
||||
|
||||
@@ -64,6 +64,7 @@ const roadtrip: TranslationStrings = {
|
||||
'roadtrip.track.errorRoute': 'Usługa wyznaczania trasy nie odpowiedziała.',
|
||||
'roadtrip.track.errorSave': 'Nie udało się zapisać punktów pośrednich.',
|
||||
'roadtrip.track.clear': 'Usuń {count} punktów pośrednich',
|
||||
'roadtrip.track.current': 'Obecnie podąża za {name}',
|
||||
'roadtrip.poi.rest': 'Miejsce odpoczynku',
|
||||
'roadtrip.poi.campsite': 'Kemping',
|
||||
'roadtrip.poi.food': 'Jedzenie',
|
||||
|
||||
@@ -64,6 +64,7 @@ const roadtrip: TranslationStrings = {
|
||||
'roadtrip.track.errorRoute': 'Сервис маршрутов не ответил.',
|
||||
'roadtrip.track.errorSave': 'Не удалось сохранить промежуточные точки.',
|
||||
'roadtrip.track.clear': 'Убрать промежуточные точки: {count}',
|
||||
'roadtrip.track.current': 'Сейчас идёт по {name}',
|
||||
'roadtrip.poi.rest': 'Зона отдыха',
|
||||
'roadtrip.poi.campsite': 'Кемпинг',
|
||||
'roadtrip.poi.food': 'Еда',
|
||||
|
||||
@@ -64,6 +64,7 @@ const roadtrip: TranslationStrings = {
|
||||
'roadtrip.track.errorRoute': 'Ruttjänsten svarade inte.',
|
||||
'roadtrip.track.errorSave': 'Mellanpunkterna kunde inte sparas.',
|
||||
'roadtrip.track.clear': 'Ta bort {count} mellanpunkter',
|
||||
'roadtrip.track.current': 'Följer just nu {name}',
|
||||
'roadtrip.poi.rest': 'Rastplats',
|
||||
'roadtrip.poi.campsite': 'Campingplats',
|
||||
'roadtrip.poi.food': 'Mat',
|
||||
|
||||
@@ -64,6 +64,7 @@ const roadtrip: TranslationStrings = {
|
||||
'roadtrip.track.errorRoute': 'Rota servisi yanıt vermedi.',
|
||||
'roadtrip.track.errorSave': 'Ara noktalar kaydedilemedi.',
|
||||
'roadtrip.track.clear': '{count} ara noktayı kaldır',
|
||||
'roadtrip.track.current': 'Şu anda {name} izini takip ediyor',
|
||||
'roadtrip.poi.rest': 'Dinlenme alanı',
|
||||
'roadtrip.poi.campsite': 'Kamp alanı',
|
||||
'roadtrip.poi.food': 'Yemek',
|
||||
|
||||
@@ -64,6 +64,7 @@ const roadtrip: TranslationStrings = {
|
||||
'roadtrip.track.errorRoute': 'Сервіс маршрутів не відповів.',
|
||||
'roadtrip.track.errorSave': 'Не вдалося зберегти проміжні точки.',
|
||||
'roadtrip.track.clear': 'Прибрати проміжні точки: {count}',
|
||||
'roadtrip.track.current': 'Зараз іде за {name}',
|
||||
'roadtrip.poi.rest': 'Зона відпочинку',
|
||||
'roadtrip.poi.campsite': 'Кемпінг',
|
||||
'roadtrip.poi.food': 'Їжа',
|
||||
|
||||
@@ -64,6 +64,7 @@ const roadtrip: TranslationStrings = {
|
||||
'roadtrip.track.errorRoute': 'Dịch vụ chỉ đường không phản hồi.',
|
||||
'roadtrip.track.errorSave': 'Không thể lưu các điểm trung gian.',
|
||||
'roadtrip.track.clear': 'Bỏ {count} điểm trung gian',
|
||||
'roadtrip.track.current': 'Hiện đang đi theo {name}',
|
||||
'roadtrip.poi.rest': 'Trạm dừng nghỉ',
|
||||
'roadtrip.poi.campsite': 'Khu cắm trại',
|
||||
'roadtrip.poi.food': 'Đồ ăn',
|
||||
|
||||
@@ -64,6 +64,7 @@ const roadtrip: TranslationStrings = {
|
||||
'roadtrip.track.errorRoute': '路線服務沒有回應。',
|
||||
'roadtrip.track.errorSave': '無法儲存途經點。',
|
||||
'roadtrip.track.clear': '移除 {count} 個途經點',
|
||||
'roadtrip.track.current': '目前沿 {name} 行駛',
|
||||
'roadtrip.poi.rest': '休息站',
|
||||
'roadtrip.poi.campsite': '露營地',
|
||||
'roadtrip.poi.food': '餐飲',
|
||||
|
||||
@@ -64,6 +64,7 @@ const roadtrip: TranslationStrings = {
|
||||
'roadtrip.track.errorRoute': '路线服务没有响应。',
|
||||
'roadtrip.track.errorSave': '无法保存途经点。',
|
||||
'roadtrip.track.clear': '移除 {count} 个途经点',
|
||||
'roadtrip.track.current': '当前沿 {name} 行驶',
|
||||
'roadtrip.poi.rest': '服务区',
|
||||
'roadtrip.poi.campsite': '露营地',
|
||||
'roadtrip.poi.food': '餐饮',
|
||||
|
||||
@@ -92,10 +92,39 @@ export const roadtripViaBatchRequestSchema = z.object({
|
||||
})).max(100),
|
||||
/** Legs to clear first, by `after_order_index`. Absent means add to what is there. */
|
||||
replace_legs: z.array(z.number().int().min(0)).max(100).optional(),
|
||||
/**
|
||||
* The imported track this chain was fitted to, if it came from one.
|
||||
*
|
||||
* Three states rather than two, which is why it is an object and not an id: absent
|
||||
* leaves whatever the day already followed alone, `null` says the day follows nothing
|
||||
* any more, and an object records the road it now takes. A bare nullable id could not
|
||||
* tell "do not touch this" apart from "forget it".
|
||||
*/
|
||||
track: z.object({
|
||||
place_id: z.number().int().positive(),
|
||||
/** How far the fitted route still ran from the track at its worst point. */
|
||||
stray_km: z.number().min(0).max(40_000).nullable().optional(),
|
||||
}).nullable().optional(),
|
||||
});
|
||||
export type RoadtripViaBatchRequest = z.infer<typeof roadtripViaBatchRequestSchema>;
|
||||
|
||||
/** Which track a day was fitted to, and how closely. */
|
||||
export const roadtripDayTrackSchema = z.object({
|
||||
day_id: z.number(),
|
||||
place_id: z.number(),
|
||||
stray_km: z.number().nullable(),
|
||||
});
|
||||
export type RoadtripDayTrack = z.infer<typeof roadtripDayTrackSchema>;
|
||||
|
||||
export const roadtripViaListResponseSchema = z.object({
|
||||
vias: z.array(roadtripViaSchema),
|
||||
/**
|
||||
* The tracks the trip's days follow, one row per day that follows one.
|
||||
*
|
||||
* Alongside the vias rather than behind a route of its own: they are read together on
|
||||
* every load of road-trip mode, and a second request for a handful of rows would be a
|
||||
* second round trip for something the first one could carry.
|
||||
*/
|
||||
tracks: z.array(roadtripDayTrackSchema),
|
||||
});
|
||||
export type RoadtripViaListResponse = z.infer<typeof roadtripViaListResponseSchema>;
|
||||
|
||||
Reference in New Issue
Block a user