Files
wiki/tasks/enduro-trails/DEV_TASK_PHASE5_UX2.md
2026-05-05 22:10:01 +03:00

8.9 KiB
Raw Permalink Blame History

Dev Task: Enduro Trails — UX маршрута + индикатор загрузки

Приоритет: HIGH
Проект: enduro-trails
Дата: 2026-05-05


Контекст

Текущий UX маршрута неудобен:

  1. После выбора двух точек автоматически открывается основной sheet — карта скрывается
  2. «Добавить точку» в основном sheet не скрывает sheet — карта не видна для тапа
  3. Основной sheet открывается сразу, хотя достаточно мини-бара
  4. Нет индикатора что маршрут строится — пользователь не понимает что происходит

Файлы: ТОЛЬКО app.js, app.css, index.html (бэкенд НЕ трогать)


Задача 1: Не открывать основной sheet автоматически

Текущее поведение

В initRouteClicks при добавлении второй точки вызывается buildRoute() — который внутри ничего не открывает, НО toggleRouteMode() при входе в режим уже открыл sheet через openSheet('sheet-route').

Нужное поведение

  • Вход в режим маршрута (toggleRouteMode()) → НЕ открывать sheet-route автоматически
  • Sheet открывается ТОЛЬКО по явному тапу на мини-бар (уже работает через showMiniRouteSheet)
  • Маршрут строится в фоне, карта остаётся видна, мини-бар показывает прогресс

Изменения в toggleRouteMode():

function toggleRouteMode() {
  const btn = document.getElementById('tb-route');

  if (routeMode) {
    // Если sheet открыт — закрыть sheet (но не выходить из режима)
    const sheet = document.getElementById('sheet-route');
    if (sheet && sheet.classList.contains('open')) {
      closeSheet('sheet-route');
      return;
    }
    // Если sheet закрыт — выйти из режима и сбросить маршрут
    routeMode = false;
    btn.classList.remove('active');
    clearRoute();
    window._map.getCanvas().style.cursor = '';
  } else {
    // Войти в режим маршрута — НЕ открывать sheet
    deactivateAllModes();
    routeMode = true;
    btn.classList.add('active');
    clearRoute();
    window._map.getCanvas().style.cursor = 'crosshair';
    // sheet НЕ открываем — пользователь сам тапнет на мини-бар
  }
  updateMapModeClass();
}

Задача 2: «Добавить точку» скрывает sheet

В addWaypointMode():

function addWaypointMode() {
  if (routeWaypoints.length >= 10) return;
  if (!routeMode) {
    routeMode = true;
    document.getElementById('tb-route').classList.add('active');
    updateMapModeClass();
  }
  addingWaypoint = true;
  window._map.getCanvas().style.cursor = 'crosshair';
  // Скрыть основной sheet чтобы карта была видна
  closeSheet('sheet-route');
  // Показать подсказку в мини-баре
  const statsEl = document.getElementById('mini-stats');
  if (statsEl) statsEl.textContent = 'Тапни на карте для добавления точки';
  document.getElementById('sheet-route-mini').classList.add('visible');
}

Задача 3: После построения маршрута — только мини-бар

В buildRoute() — убрать автоматическое открытие sheet, показывать только мини-бар:

async function buildRoute() {
  if (routeWaypoints.length < 2) return;
  
  // Показать мини-бар с индикатором загрузки
  showMiniRouteLoading();
  
  // Закрыть основной sheet если открыт
  closeSheet('sheet-route');
  
  document.getElementById('route-status').textContent = 'Строю маршрут...';
  
  try {
    const resp = await fetch(...);
    // ... существующая логика ...
    
    drawRouteResults(routeResults, 0);
    
    // После успеха — показать мини-бар с результатом (НЕ открывать sheet)
    showMiniRouteSheet();
    hideMiniRouteLoading();
    document.getElementById('route-status').textContent = `${routeResults.length} маршрут(ов)`;
  } catch(e) {
    hideMiniRouteLoading();
    document.getElementById('route-status').textContent = '❌ ' + e.message;
    // Показать ошибку в мини-баре
    const statsEl = document.getElementById('mini-stats');
    if (statsEl) statsEl.textContent = '❌ ' + e.message;
  }
}

Задача 4: Индикатор загрузки — колесо мотоцикла

Анимация

В мини-баре (#sheet-route-mini) показывать SVG колесо мотоцикла которое крутится пока строится маршрут.

SVG колесо (кросс/эндуро стиль — спицы, грунтозацепы):

<svg class="moto-wheel" viewBox="0 0 40 40" xmlns="http://www.w3.org/2000/svg">
  <!-- Обод -->
  <circle cx="20" cy="20" r="17" fill="none" stroke="var(--accent)" stroke-width="2.5"/>
  <!-- Шина (внешний контур с грунтозацепами — неровный) -->
  <circle cx="20" cy="20" r="19" fill="none" stroke="var(--text2)" stroke-width="1.5" stroke-dasharray="3 2"/>
  <!-- Ступица -->
  <circle cx="20" cy="20" r="3" fill="var(--accent)"/>
  <!-- Спицы (8 штук) -->
  <line x1="20" y1="3" x2="20" y2="17" stroke="var(--text2)" stroke-width="1.2" stroke-linecap="round"/>
  <line x1="20" y1="23" x2="20" y2="37" stroke="var(--text2)" stroke-width="1.2" stroke-linecap="round"/>
  <line x1="3" y1="20" x2="17" y2="20" stroke="var(--text2)" stroke-width="1.2" stroke-linecap="round"/>
  <line x1="23" y1="20" x2="37" y2="20" stroke="var(--text2)" stroke-width="1.2" stroke-linecap="round"/>
  <line x1="7.9" y1="7.9" x2="17.5" y2="17.5" stroke="var(--text2)" stroke-width="1.2" stroke-linecap="round"/>
  <line x1="22.5" y1="22.5" x2="32.1" y2="32.1" stroke="var(--text2)" stroke-width="1.2" stroke-linecap="round"/>
  <line x1="32.1" y1="7.9" x2="22.5" y2="17.5" stroke="var(--text2)" stroke-width="1.2" stroke-linecap="round"/>
  <line x1="17.5" y1="22.5" x2="7.9" y2="32.1" stroke="var(--text2)" stroke-width="1.2" stroke-linecap="round"/>
</svg>

CSS анимация:

.moto-wheel {
  width: 32px; height: 32px;
  animation: wheelSpin 0.8s linear infinite;
  display: none;
  flex-shrink: 0;
}
.moto-wheel.spinning {
  display: block;
}
@keyframes wheelSpin {
  from { transform: rotate(0deg); }
  to { transform: rotate(360deg); }
}

Где показывать

В #sheet-route-mini добавить элемент колеса рядом с #mini-stats:

<!-- В index.html, внутри #sheet-route-mini -->
<svg id="mini-wheel" class="moto-wheel" viewBox="0 0 40 40" ...>...</svg>

Функции управления:

function showMiniRouteLoading() {
  const wheel = document.getElementById('mini-wheel');
  const statsEl = document.getElementById('mini-stats');
  if (wheel) wheel.classList.add('spinning');
  if (statsEl) statsEl.textContent = 'Строю маршрут...';
  document.getElementById('sheet-route-mini').classList.add('visible');
}

function hideMiniRouteLoading() {
  const wheel = document.getElementById('mini-wheel');
  if (wheel) wheel.classList.remove('spinning');
}

Итоговый UX-флоу

Тап на 🗺 → routeMode=true, cursor=crosshair, sheet НЕ открывается
Тап на карте (точка A) → маркер A, cursor=crosshair
Тап на карте (точка B) → маркер B, мини-бар появляется с крутящимся колесом
Маршрут построен → колесо исчезает, мини-бар показывает "120 км · 87% грунт"
Тап на мини-бар → открывается основной sheet с деталями
Тап «Добавить точку» в sheet → sheet скрывается, карта видна, cursor=crosshair
Тап на карте → точка добавлена, маршрут перестраивается (колесо в мини-баре)

Деплой

/tmp/deploy_static.js
SSH: host=82.22.50.71, port=22, user=slin, pass=motoZ@yaz2010
Health: curl -s http://mva154:5558/api/health

Ограничения

  • НЕ трогать app.py
  • Без npm-зависимостей
  • Сохранить всю существующую логику (drag-and-drop, geocoding, GPX, etc.)