auto-sync: 2026-05-05 20:10:01

This commit is contained in:
Stream
2026-05-05 20:10:01 +03:00
parent 4d0dc890ee
commit 184253fa53
2 changed files with 143 additions and 1 deletions

View File

@@ -266,8 +266,24 @@ body.has-map-mode #sheet-backdrop.visible { pointer-events: none; }
display: flex; align-items: center; gap: 8px;
padding: 6px 0;
border-bottom: 1px solid var(--border);
position: relative;
}
.wl-item:last-child { border-bottom: none; }
.wl-drag-handle {
width: 20px; height: 28px;
display: flex; align-items: center; justify-content: center;
color: var(--text3); cursor: grab; flex-shrink: 0;
touch-action: none;
-webkit-tap-highlight-color: transparent;
}
.wl-drag-handle svg { width: 16px; height: 16px; }
.wl-item.dragging {
opacity: 0.4;
background: var(--surface);
border-radius: 4px;
}
.wl-item.drag-over-top { border-top: 2px solid var(--accent); }
.wl-item.drag-over-bottom { border-bottom: 2px solid var(--accent); }
.wl-pin { flex-shrink: 0; display: flex; align-items: center; }
.wl-label {
flex: 1; font-size: 13px; color: var(--text);

View File

@@ -532,15 +532,18 @@ async function renderWaypointsList() {
const list = document.getElementById('waypoints-list');
if (!routeWaypoints.length) { list.innerHTML = ''; return; }
const gripSvg = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="9" cy="6" r="1"/><circle cx="15" cy="6" r="1"/><circle cx="9" cy="12" r="1"/><circle cx="15" cy="12" r="1"/><circle cx="9" cy="18" r="1"/><circle cx="15" cy="18" r="1"/></svg>`;
let html = routeWaypoints.map((wp, i) => {
const isStart = i === 0;
const isEnd = i === routeWaypoints.length - 1;
const label = isStart ? 'S' : isEnd ? 'F' : String(i);
const color = isStart ? '#2EA043' : isEnd ? '#FF3B1F' : '#0066ff';
const coordText = `${wp.lat.toFixed(3)}, ${wp.lon.toFixed(3)}`;
return `<div class="wl-item" id="wl-item-${i}">
return `<div class="wl-item" id="wl-item-${i}" data-idx="${i}">
<div class="wl-pin">${waypointPinSvg(label, color)}</div>
<span class="wl-label" id="wl-label-${i}">${coordText}</span>
<div class="wl-drag-handle" data-idx="${i}">${gripSvg}</div>
<button class="wl-remove" onclick="removeWaypoint(${i})" title="Удалить">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6 6 18M6 6l12 12"/></svg>
</button>
@@ -563,6 +566,129 @@ async function renderWaypointsList() {
const el = document.getElementById(`wl-label-${i}`);
if (el) el.textContent = name;
});
// Touch drag-and-drop (mobile only)
_initWaypointDragHandles(list);
}
function _initWaypointDragHandles(list) {
let dragIdx = -1;
let startY = 0;
let dragging = false;
let lastOverEl = null;
let lastOverPos = null;
function getItemEls() {
return Array.from(list.querySelectorAll('.wl-item[data-idx]'));
}
function clearHighlights() {
getItemEls().forEach(el => {
el.classList.remove('drag-over-top', 'drag-over-bottom', 'dragging');
});
}
function getDropTarget(clientY) {
const items = getItemEls();
for (const el of items) {
const idx = parseInt(el.dataset.idx, 10);
if (idx === dragIdx) continue;
const rect = el.getBoundingClientRect();
if (clientY >= rect.top && clientY <= rect.bottom) {
const mid = rect.top + rect.height / 2;
return { el, idx, pos: clientY < mid ? 'top' : 'bottom' };
}
}
return null;
}
function startDrag(clientY, idx) {
dragIdx = idx;
startY = clientY;
dragging = false;
lastOverEl = null;
lastOverPos = null;
const dragEl = document.getElementById(`wl-item-${idx}`);
if (dragEl) dragEl.classList.add('dragging');
}
function moveDrag(clientY) {
if (dragIdx < 0) return;
const dy = Math.abs(clientY - startY);
if (dy > 5) dragging = true;
if (!dragging) return;
clearHighlights();
const target = getDropTarget(clientY);
if (target) {
lastOverEl = target.el;
lastOverPos = target.pos;
target.el.classList.add(target.pos === 'top' ? 'drag-over-top' : 'drag-over-bottom');
} else {
lastOverEl = null;
lastOverPos = null;
}
}
function endDrag(finalClientY) {
if (dragIdx < 0) return;
clearHighlights();
const dy = Math.abs(finalClientY - startY);
if (dragging && dy > 30 && lastOverEl !== null) {
const dropIdx = parseInt(lastOverEl.dataset.idx, 10);
let insertAt = lastOverPos === 'top' ? dropIdx : dropIdx + 1;
const moved = routeWaypoints.splice(dragIdx, 1)[0];
if (insertAt > dragIdx) insertAt--;
routeWaypoints.splice(insertAt, 0, moved);
rebuildWaypointMarkers();
renderWaypointsList();
if (routeWaypoints.length >= 2) debounceBuildRoute();
updateMiniRouteCard();
}
dragIdx = -1;
dragging = false;
lastOverEl = null;
lastOverPos = null;
}
// Touch (mobile)
list.addEventListener('touchstart', (e) => {
const handle = e.target.closest('.wl-drag-handle');
if (!handle) return;
startDrag(e.touches[0].clientY, parseInt(handle.dataset.idx, 10));
}, { passive: true });
list.addEventListener('touchmove', (e) => {
if (dragIdx < 0) return;
moveDrag(e.touches[0].clientY);
e.preventDefault();
}, { passive: false });
list.addEventListener('touchend', (e) => {
endDrag(e.changedTouches[0].clientY);
}, { passive: true });
// Mouse (desktop)
list.addEventListener('mousedown', (e) => {
const handle = e.target.closest('.wl-drag-handle');
if (!handle) return;
e.preventDefault();
startDrag(e.clientY, parseInt(handle.dataset.idx, 10));
document.addEventListener('mousemove', _onDragMouse);
document.addEventListener('mouseup', _onDropMouse);
});
function _onDragMouse(e) {
if (dragIdx < 0) return;
moveDrag(e.clientY);
}
function _onDropMouse(e) {
endDrag(e.clientY);
document.removeEventListener('mousemove', _onDragMouse);
document.removeEventListener('mouseup', _onDropMouse);
}
}
function removeWaypoint(idx) {