diff --git a/PROJECT_MAP.md b/PROJECT_MAP.md index 0c64578..b7bf6b8 100644 --- a/PROJECT_MAP.md +++ b/PROJECT_MAP.md @@ -21,7 +21,7 @@ | Файл | Строк | Назначение | |------|-------|------------| -| `js/main.js` | ~5900 | Экраны, навигация, модалки, графики, нутриенты, HR UI, компас (EMA + gimbal lock), ландшафт (3 режима), дороги. `appConfirm`/`appPrompt` (in-app диалоги вместо нативных, в стеке окон). Страница «Сохранённые маршруты», оверлей маршрута на записи и просмотре (`whenMapReady` — await готовности карты, не таймаут). Панель де-джиттера на детали трека (ползунки окно/итерации, живое превью, сохранить/откат, `reopenDetail` — переоткрытие детали без наложения оверлеев). `showRouteDetail` — предпросмотр маршрута (карта+профиль высоты+тап-высоты+фуллскрин), `drawRoutePolyline`/`routeSlopeColor`/`routeElevColor` — топо/уклон-обводка в отдельной панели `routePane` (маршрут ПОД треком), тоггл уклон/высота. `fetchElevations` — цепочка GPXZ→open-elevation→open-meteo→OpenTopoData | +| `js/main.js` | ~5900 | Экраны, навигация, модалки, графики, нутриенты, HR UI, компас (EMA + gimbal lock), ландшафт (3 режима), дороги. `appConfirm`/`appPrompt` (in-app диалоги вместо нативных, в стеке окон). Страница «Сохранённые маршруты», оверлей маршрута на записи и просмотре (`whenMapReady` — await готовности карты, не таймаут). Панель де-джиттера на детали трека (ползунки окно/итерации, живое превью, сохранить/откат, `reopenDetail` — переоткрытие детали без наложения оверлеев). `showRouteDetail` — предпросмотр маршрута (карта+профиль высоты+тап-высоты+фуллскрин), `showRouteEditor` — создание/правка маршрута (тап=точка, тащить=двигать, тап точки=удалить). `drawRoutePolyline`/`routeSlopeColor` (полосы уклона по 1%)/`routeElevColor` — топо/уклон-обводка в панели `routePane` (маршрут ПОД треком; на rotate-карте панель вешается внутрь `_rotatePane`, иначе маршрут не виден при записи). `fetchElevations` — цепочка GPXZ→open-elevation→open-meteo→OpenTopoData. Диагностические `console.log("[тег]...")` глушатся в release по `get_app_info().debug` (cfg!(debug_assertions)) | | `js/db.js` | ~1550 | SQLite через Tauri IPC. Миграции, CRUD, 41 поле добавок, water_pct, reserves, sync. Хелперы маршрутов (routes CRUD, `updateRoutePoints` — сохранить высоты, `getRoutes.hasEle`). `saveSmoothedTrack` — чанковый `CASE`-апдейт lat/lon_smoothed (18 UPDATE на 4366 точек вместо 4366), `setSessionSmoothed` — откат де-джиттера | | `js/gps.js` | ~1650 | GPS запись, ккал (нетто-модель + MET-нетто floor, накат=базовый), altitude filter + гистерезис высоты, BLE HR polling, dead reckoning. Источник истины БД при живой записи — нативный файл (`_processIncomingPoint(p, fromNative)`: watchPosition только рисует, поллинг пишет). `recoverFromFiles` — восстановление точек с устройства (матч по id/дате + `_s` в имени, дедуп ±2.5с, `recalculateSession(noDelete)`, чистка импортированных файлов). `recomputeAllStats` — пересчёт статы всех треков без изменения точек. Отбор по точности (minAccuracyM), bearing_deg. `smoothTrackSG(pts,W,it)` — де-джиттер Савицкого-Голея (квадратичный МНК по окну, итеративно, концы закреплены; lat/lon→метры→обратно, все поля точки сохраняются). `parseRouteGPX(xml)` — парсер маршрутов (`rtept`/`trkpt`/`wpt`, читает ``, DOMParser). `routeElevStats(pts)` — профиль маршрута (накопл. дистанция, грейд к след. точке, набор/сброс с гистерезисом, макс-уклон; высоты сглаживаются). `config.gpxzKey` — ключ GPXZ (localStorage) | | `js/sync.js` | ~300 | Синхронизация между устройствами (LAN, PHP сервер на роутере) | @@ -153,7 +153,7 @@ | `get_points_filename` | Имя активного файла точек (по указателю `gps_live_points.jsonl`) | | `debug_native_paths` | Диагностика путей нативных файлов | | `save_file` | SAF диалог сохранения (GPX экспорт) | -| `get_app_info` | Версия, git hash, дата сборки | +| `get_app_info` | Версия, git hash, дата сборки, `debug` (cfg!(debug_assertions) — для глушения диагностики в release) | | `get_update_info` | Проверка OTA обновления (BUILD_NUMBER с VPS) | | `ble_scan` | Сканирование BLE устройств | | `ble_scan_results` | Результаты скана (Android) | diff --git a/README.md b/README.md index 58333a7..05ab8d1 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,8 @@ - **OTA обновления** — проверка BUILD_NUMBER на роутере (LAN) или VPS, скачивание через браузер. - **Ручные маршруты** — рисование трека на карте тапами, elevation из прошлых треков или open-meteo API. - **Сохранённые маршруты** — импорт GPX-маршрутов, нарисованных заранее (например, в конструкторе Яндекс.Карт: формат ``), выбор активного, наложение цветной линией поверх карты во время записи И на экране просмотра прошлого трека — для визуальной навигации. Управление на отдельной странице (импорт/переименование/цвет/удаление, один активный за раз), переключение линии кнопкой-тогглом. Цвета «проезженных дорог» настраиваются раздельно для вело/пеших/бега. -- **Высоты маршрута (рельеф до поездки)** — экран предпросмотра маршрута (тап по названию): профиль высоты (крутые участки красным), длина/набор/сброс/макс. уклон, тап по точке — высота и уклон, разворот карты на весь экран. Топографическая обводка линии с тогглом **уклон/высота**: уклон по градусам (салатовый <2°, жёлтый 2–4°, красный 4–6°, чёрный >6°) или гипсометрия (зелёный→жёлтый→коричневый). Обводка и тоггл доступны и в оверлее на карте записанного трека; маршрут рисуется ПОД треком. Высоты (`` из GPX или онлайн) тянутся по цепочке: **GPXZ** (Copernicus GLO-30, точнее для уклонов; ключ в GPS-настройках, хранится только на устройстве) → open-elevation → open-meteo → OpenTopoData (без ключа). Калории пересчитываются по сглаженным точкам; для уклона высоты сглаживаются (гасит вертикальный шум SRTM). +- **Высоты маршрута (рельеф до поездки)** — экран предпросмотра маршрута (тап по названию): профиль высоты (крутые участки красным), длина/набор/сброс/макс. уклон, тап по точке — высота и уклон, разворот карты на весь экран. Топографическая обводка линии с тогглом **уклон/высота**: уклон дискретными полосами по 1% (зелёный→жёлтый→красный→коричневый→чёрный, ≥10% чёрный) или гипсометрия по высоте (зелёный→жёлтый→коричневый). Обводка и тоггл доступны и в оверлее на карте записанного трека и во время записи; маршрут рисуется ПОД треком. Высоты (`` из GPX или онлайн) тянутся по цепочке: **GPXZ** (Copernicus GLO-30, точнее для уклонов; ключ в GPS-настройках, хранится только на устройстве) → open-elevation → open-meteo → OpenTopoData (без ключа). Калории пересчитываются по сглаженным точкам; для уклона высоты сглаживаются (гасит вертикальный шум SRTM). +- **Редактор маршрутов** — создание («Новый маршрут») и правка сохранённого маршрута прямо в приложении: тап по карте — добавить точку, тащить точку — двигать, тап по точке — удалить. Высоты подтягиваются онлайн после отрисовки (из предпросмотра). - **Де-джиттер трека** — сглаживание дрожащего GPS-трека методом Савицкого-Голея (настраиваемое окно/число итераций, живое превью на карте детали трека). Хранится обратимо: оригинал и сглаженный вариант лежат раздельно, калории пересчитываются по сглаженным точкам, откат к оригиналу в один тап. Точность каждого фикса (`accuracy_m`) пишется в БД для будущего пересчёта с порогом точности. - **Экспорт/импорт** — свои продукты, рецепты и добавки одним файлом (JSON). - **Вес, сон, добавки** — логирование с историей. diff --git a/frontend/js/main.js b/frontend/js/main.js index 49c0bee..d1252d2 100644 --- a/frontend/js/main.js +++ b/frontend/js/main.js @@ -123,6 +123,21 @@ async function init() { return; } + // Diagnostic logs stay in the source but only fire in debug builds. Rust reports + // the compiled build profile (cfg!(debug_assertions)); in a release build we + // silence console.log lines that start with a diagnostic tag ([poll]/[recover]/ + // [stop]/[GPS]/[HR]/[SD]/...). console.error/warn are never touched. This means + // no adding/removing logs by hand — a tagged log auto-disappears in release. + try { + let info = await window.__TAURI__.core.invoke("get_app_info"); + window.__BALDAV_DEBUG = !!info.debug; + } catch { window.__BALDAV_DEBUG = false; } + if (!window.__BALDAV_DEBUG) { + let _log = console.log.bind(console); + let DIAG = /^\[(poll|recover|stop|GPS|HR|SD|seq|native|flush|watch|import|rec)\]/; + console.log = function (...a) { if (typeof a[0] === "string" && DIAG.test(a[0])) return; _log.apply(console, a); }; + } + // Show loading immediately so the user sees something app.innerHTML = `
Загрузка...
@@ -2893,14 +2908,23 @@ async function drawActiveRouteOverlay(map, prev) { return drawRoutePolyline(map, route.points, route.color); } -// Slope tint by absolute grade in DEGREES: gentle = lime, then yellow, red, and -// black for anything a singlespeed can't pedal up / safely coast down (>6°). +// Slope tint by absolute grade in %, in 1%-wide discrete bands (hard boundaries +// kept — one colour per segment): green -> yellow -> red -> brown -> black. +// <1%=0 .. 9–10%=9, >=10% = black. Both up- and downhill coloured by magnitude. +let ROUTE_SLOPE_BANDS = [ + "#2e7d32", "#7cb342", "#aeea00", "#ffeb3b", "#ffc107", + "#ff9800", "#ff5722", "#e53935", "#b71c1c", "#6d4c41", "#000000", +]; function routeSlopeColor(gradePct) { - let deg = Math.abs(Math.atan((gradePct || 0) / 100) * 180 / Math.PI); - if (deg < 2) return "#aeea00"; // салатовый — пологий (<2°) - if (deg < 4) return "#ffd600"; // жёлтый (2–4°) - if (deg < 6) return "#e53935"; // красный (4–6°) - return "#000000"; // >6° — чёрный + let idx = Math.min(10, Math.floor(Math.abs(gradePct || 0))); + return ROUTE_SLOPE_BANDS[idx]; +} +// Compact colour strip for the slope legend (every 1% band, 0 → 10%+). +function slopeLegendHtml() { + let cells = ROUTE_SLOPE_BANDS + .map(c => ``) + .join(""); + return `уклон 0% ${cells} 10%+`; } // Draw a route as a feature group: a wide colored casing (per segment — by slope @@ -2910,7 +2934,13 @@ function routeSlopeColor(gradePct) { // draws ON TOP of the route. Removable via map.removeLayer. function drawRoutePolyline(map, pts, color, mode) { mode = mode || localStorage.getItem("baldav_route_color_mode") || "slope"; - if (!map.getPane("routePane")) { map.createPane("routePane"); map.getPane("routePane").style.zIndex = 250; } + // On a rotating map (leaflet-rotate) tile/overlay panes live INSIDE _rotatePane; + // a pane created at the default mapPane level wouldn't rotate/zoom with the map + // (route flashes then vanishes). Parent our pane to _rotatePane when it exists. + if (!map.getPane("routePane")) { + map.createPane("routePane", map._rotatePane || undefined); + map.getPane("routePane").style.zIndex = 250; // between tilePane(200) and overlayPane(400) + } let popts = { pane: "routePane", interactive: false }; let latlngs = pts.map(p => [p.lat, p.lon]); let group = L.featureGroup(); @@ -2959,7 +2989,10 @@ async function showRoutesScreen(container) {

Сохранённые маршруты

- +
+ + +