8.5 KiB
8.5 KiB
Dev Task: Enduro Trails — Мини-бар маршрута
Файлы: /home/node/.openclaw/workspace/tasks/enduro-trails/prototype/static/
Деплой: /tmp/deploy_static.js
Три состояния sheet-route
| Состояние | Как попасть | Что происходит |
|---|---|---|
| Полная панель | Тап «Маршрут» в toolbar | Открыта sheet-route |
| Мини-бар | Свайп вниз по handle полной панели | Панель свёрнута, маршрут на карте остаётся |
| Закрыто | Крестик (X) в полной панели | Панель закрыта + clearRoute() |
Мини-бар → тап или свайп вверх → полная панель. Мини-бар → свайп влево/вправо → переключение вариантов маршрута.
HTML — добавить в index.html перед </body>
<div id="sheet-route-mini">
<div class="mini-handle" id="mini-route-handle"></div>
<div class="mini-route-info">
<div class="mini-route-dot" id="mini-dot"></div>
<div class="mini-route-text">
<div class="mini-route-label" id="mini-label">Вариант 1</div>
<div class="mini-route-stats" id="mini-stats">— км · —% грунт</div>
</div>
<div class="mini-route-arrows">
<span class="mini-arrow" id="mini-prev">‹</span>
<span class="mini-arrow" id="mini-next">›</span>
</div>
</div>
</div>
CSS — добавить в app.css
/* ── Mini Route Bar ───────────────────────── */
#sheet-route-mini {
position: fixed;
bottom: 72px; left: 0; right: 0;
height: 64px;
background: var(--surface);
border-top: 1px solid var(--border);
border-radius: 14px 14px 0 0;
z-index: 350;
display: none;
flex-direction: column;
align-items: center;
box-shadow: 0 -4px 16px var(--shadow);
}
#sheet-route-mini.visible { display: flex; }
#sheet-route-mini .mini-handle {
width: 32px; height: 4px;
background: var(--border2, var(--border));
border-radius: 2px;
margin: 7px auto 0;
flex-shrink: 0;
}
.mini-route-info {
display: flex; align-items: center;
gap: 10px; padding: 0 16px;
flex: 1; width: 100%;
}
.mini-route-dot { width: 12px; height: 12px; border-radius: 50%; flex-shrink: 0; }
.mini-route-text { flex: 1; min-width: 0; }
.mini-route-label { font-size: 13px; font-weight: 700; color: var(--text); }
.mini-route-stats { font-size: 11px; color: var(--text2); }
.mini-route-arrows { display: flex; gap: 4px; flex-shrink: 0; }
.mini-arrow {
width: 28px; height: 28px;
display: flex; align-items: center; justify-content: center;
background: var(--surface2); border: 1px solid var(--border);
border-radius: 8px; font-size: 18px; color: var(--text2);
cursor: pointer; user-select: none; -webkit-tap-highlight-color: transparent;
}
.mini-arrow:active { background: var(--accent); color: #fff; border-color: var(--accent); }
@media (min-width: 768px) {
#sheet-route-mini { left: 72px; width: 380px; right: auto; border-radius: 0 14px 0 0; }
}
JS — добавить функции в app.js
// ─── Mini Route Bar ────────────────────────────────────────────────
const ROUTE_COLORS = ['#0066ff','#ff6600','#00aa44','#aa00ff','#ff0044'];
function showMiniRouteSheet() {
if (!routeResults || routeResults.length === 0) return;
updateMiniRouteCard();
document.getElementById('sheet-route-mini').classList.add('visible');
initMiniRouteInteraction();
}
function hideMiniRouteSheet() {
const el = document.getElementById('sheet-route-mini');
if (el) el.classList.remove('visible');
}
function updateMiniRouteCard() {
const r = routeResults[activeRouteIdx];
if (!r) return;
const km = (r.distance_m / 1000).toFixed(0);
const dirt = r.stats?.dirt_total_pct ?? '?';
document.getElementById('mini-dot').style.background = ROUTE_COLORS[activeRouteIdx % ROUTE_COLORS.length];
document.getElementById('mini-label').textContent = `Вариант ${activeRouteIdx + 1} из ${routeResults.length}`;
document.getElementById('mini-stats').textContent = `${km} км · ${dirt}% грунт`;
document.getElementById('mini-prev').style.opacity = activeRouteIdx > 0 ? '1' : '0.3';
document.getElementById('mini-next').style.opacity = activeRouteIdx < routeResults.length - 1 ? '1' : '0.3';
}
function selectMiniRoute(idx) {
if (idx < 0 || idx >= routeResults.length) return;
activeRouteIdx = idx;
const map = window._map;
for (let i = 0; i < routeResults.length; i++) {
const op = i === idx ? 1 : 0.35;
if (map.getLayer('route-line-' + i)) map.setPaintProperty('route-line-' + i, 'line-opacity', op);
if (map.getLayer('route-line-' + i + '-outline')) map.setPaintProperty('route-line-' + i + '-outline', 'line-opacity', op);
}
updateMiniRouteCard();
}
function initMiniRouteInteraction() {
const mini = document.getElementById('sheet-route-mini');
if (!mini) return;
// Re-bind arrow buttons
document.getElementById('mini-prev').onclick = (e) => { e.stopPropagation(); selectMiniRoute(activeRouteIdx - 1); };
document.getElementById('mini-next').onclick = (e) => { e.stopPropagation(); selectMiniRoute(activeRouteIdx + 1); };
// Remove old touch listeners by replacing element
const newMini = mini.cloneNode(true);
mini.parentNode.replaceChild(newMini, mini);
document.getElementById('mini-prev').onclick = (e) => { e.stopPropagation(); selectMiniRoute(activeRouteIdx - 1); };
document.getElementById('mini-next').onclick = (e) => { e.stopPropagation(); selectMiniRoute(activeRouteIdx + 1); };
let startX = 0, startY = 0;
newMini.addEventListener('touchstart', e => {
startX = e.touches[0].clientX;
startY = e.touches[0].clientY;
}, { passive: true });
newMini.addEventListener('touchend', e => {
const dx = e.changedTouches[0].clientX - startX;
const dy = e.changedTouches[0].clientY - startY;
if (Math.abs(dy) > Math.abs(dx)) {
if (dy < -40) { hideMiniRouteSheet(); openSheet('sheet-route'); }
} else {
if (dx < -40) selectMiniRoute(activeRouteIdx + 1);
if (dx > 40) selectMiniRoute(activeRouteIdx - 1);
}
});
newMini.addEventListener('click', e => {
if (e.target.classList.contains('mini-arrow')) return;
hideMiniRouteSheet();
openSheet('sheet-route');
});
}
Изменить существующие функции
minimizeSheet(id)
function minimizeSheet(id) {
closeSheet(id);
if (id === 'sheet-route' && routeResults && routeResults.length > 0) {
showMiniRouteSheet();
}
}
clearRoute() — в самое начало добавить:
hideMiniRouteSheet();
toggleRouteMode() — при входе в режим добавить перед openSheet:
hideMiniRouteSheet();
initSheetSwipe() — найти обработчик touchend и изменить логику закрытия:
Найти строку вида if (dy > 80) closeSheet(sheet.id) или if (dy > 80) { closeSheet(...) } и заменить на:
if (dy > 80) {
if (sheet.id === 'sheet-route' && routeResults && routeResults.length > 0) {
minimizeSheet(sheet.id);
} else {
closeSheet(sheet.id);
}
sheet.style.transform = '';
}
Крестик (X) в sheet-route — уже вызывает minimizeSheet('sheet-route').
Нужно изменить на toggleRouteMode() чтобы крестик стирал маршрут: В index.html найти:
<button class="sheet-close" onclick="minimizeSheet('sheet-route')">
Заменить на:
<button class="sheet-close" onclick="toggleRouteMode()">
(toggleRouteMode при routeMode=true → выходит из режима + clearRoute)
Проверка после деплоя
- Построить маршрут → свайп вниз по handle → появляется мини-бар
- Свайп влево/вправо на мини-баре → переключение вариантов (если >1)
- Тап на мини-бар → раскрывается полная панель
- Крестик в полной панели → всё закрывается, маршрут стирается с карты
- Мини-бар не появляется если маршрут не построен