diff --git a/src/plugins/layers/addMinimizeToggle.js b/src/plugins/layers/addMinimizeToggle.js index ad4ffd37..19ebbee1 100644 --- a/src/plugins/layers/addMinimizeToggle.js +++ b/src/plugins/layers/addMinimizeToggle.js @@ -6,7 +6,6 @@ export function addMinimizeToggle(element, storageKey, options = {}) { const { contentClassName = 'panel-content', buttonClassName = 'panel-minimize-btn', - titleColor = '#00b4ff', getIsMinimized, onToggle, persist = true, @@ -25,17 +24,9 @@ export function addMinimizeToggle(element, storageKey, options = {}) { const header = element.firstElementChild; if (!header) return; - const existingTitle = header.querySelector('[data-drag-handle="true"]'); const existingButton = header.querySelector(`.${buttonClassName}`); const existingWrapper = element.querySelector(`.${contentClassName}`); - if (existingTitle) { - existingTitle.style.fontFamily = "'JetBrains Mono', monospace"; - existingTitle.style.fontSize = '13px'; - existingTitle.style.fontWeight = '700'; - existingTitle.style.color = titleColor; - } - const readState = () => { if (typeof getIsMinimized === 'function') return !!getIsMinimized(); return localStorage.getItem(minimizeKey) === 'true'; @@ -49,7 +40,8 @@ export function addMinimizeToggle(element, storageKey, options = {}) { const syncState = (button, wrapper) => { const isMinimized = readState(); wrapper.style.display = isMinimized ? 'none' : 'block'; - button.innerHTML = isMinimized ? '▶' : '▼'; + button.innerHTML = '▶'; + button.style.transform = isMinimized ? 'rotate(0deg)' : 'rotate(90deg)'; element.style.cursor = isMinimized ? 'pointer' : 'default'; }; @@ -68,7 +60,8 @@ export function addMinimizeToggle(element, storageKey, options = {}) { e.stopPropagation(); const next = existingWrapper.style.display !== 'none'; existingWrapper.style.display = next ? 'none' : 'block'; - existingButton.innerHTML = next ? '▶' : '▼'; + existingButton.innerHTML = '▶'; + existingButton.style.transform = next ? 'rotate(90deg)' : 'rotate(0deg)'; element.style.cursor = next ? 'pointer' : 'default'; writeState(next); }, @@ -87,23 +80,14 @@ export function addMinimizeToggle(element, storageKey, options = {}) { const minimizeBtn = document.createElement('button'); minimizeBtn.className = buttonClassName; - minimizeBtn.innerHTML = '▼'; + minimizeBtn.innerHTML = '▶'; minimizeBtn.style.cssText = ` display: inline-flex; align-items: center; justify-content: center; - width: 16px; - min-width: 16px; - height: 16px; - background: none; - border: none; - color: #888; cursor: pointer; user-select: none; - padding: 2px 4px; - margin: 0; - font-size: 10px; - line-height: 1; + transform: rotate(0deg); `; minimizeBtn.title = 'Minimize/Maximize'; minimizeBtn.addEventListener( @@ -122,12 +106,6 @@ export function addMinimizeToggle(element, storageKey, options = {}) { title.textContent = header.textContent.replace(/[▼▶]/g, '').trim(); title.dataset.dragHandle = 'true'; title.style.flex = '1'; - title.style.cursor = 'grab'; - title.style.userSelect = 'none'; - title.style.fontFamily = "'JetBrains Mono', monospace"; - title.style.fontSize = '13px'; - title.style.fontWeight = '700'; - title.style.color = titleColor; header.textContent = ''; header.appendChild(title); @@ -141,7 +119,7 @@ export function addMinimizeToggle(element, storageKey, options = {}) { e.stopPropagation(); const hidden = contentWrapper.style.display === 'none'; contentWrapper.style.display = hidden ? 'block' : 'none'; - minimizeBtn.innerHTML = hidden ? '▼' : '▶'; + minimizeBtn.style.transform = hidden ? 'rotate(90deg)' : 'rotate(0deg)'; element.style.cursor = hidden ? 'default' : 'pointer'; writeState(!hidden); }, diff --git a/src/plugins/layers/makeDraggable.js b/src/plugins/layers/makeDraggable.js index f805bc7f..a8584d0b 100644 --- a/src/plugins/layers/makeDraggable.js +++ b/src/plugins/layers/makeDraggable.js @@ -32,7 +32,22 @@ function clampToViewport(el, margin = 40) { el.style.top = top + 'px'; } -export function makeDraggable(el, storageKey, skipPositionLoad = false) { +/** + * Helper for snapping to a grid + */ +function snapToGrid(value, gridSize) { + if (!gridSize) return value; + return Math.round(value / gridSize) * gridSize; +} + +export function makeDraggable( + el, + storageKey, + { + skipPositionLoad = false, + snap = 0, // pixels; 0 disables snapping + } = {}, +) { if (!el) return; // Cancel any previous listeners for this storageKey (e.g. after layout change) @@ -142,6 +157,7 @@ export function makeDraggable(el, storageKey, skipPositionLoad = false) { (e) => { if (e.button !== 0) return; isDragging = true; + updateCursor(); didDrag = false; startX = e.clientX; startY = e.clientY; @@ -162,11 +178,20 @@ export function makeDraggable(el, storageKey, skipPositionLoad = false) { 'mousemove', (e) => { if (!isDragging) return; + if (!didDrag && (Math.abs(e.clientX - startX) > 2 || Math.abs(e.clientY - startY) > 2)) { didDrag = true; } - el.style.left = startLeft + (e.clientX - startX) + 'px'; - el.style.top = startTop + (e.clientY - startY) + 'px'; + + let nextLeft = startLeft + (e.clientX - startX); + let nextTop = startTop + (e.clientY - startY); + + // Snap to grid if enabled + nextLeft = snapToGrid(nextLeft, snap); + nextTop = snapToGrid(nextTop, snap); + + el.style.left = nextLeft + 'px'; + el.style.top = nextTop + 'px'; }, { signal }, ); @@ -174,7 +199,7 @@ export function makeDraggable(el, storageKey, skipPositionLoad = false) { // --- Mouseup: stop drag, clamp, save --- document.addEventListener( 'mouseup', - () => { + (e) => { if (!isDragging) return; isDragging = false; el.style.opacity = '1'; @@ -182,7 +207,11 @@ export function makeDraggable(el, storageKey, skipPositionLoad = false) { updateCursor(); suppressClick = didDrag; - // Clamp so element can't be lost off-screen + if (snap) { + el.style.left = snapToGrid(el.offsetLeft, snap) + 'px'; + el.style.top = snapToGrid(el.offsetTop, snap) + 'px'; + } + clampToViewport(el); const topPercent = (el.offsetTop / window.innerHeight) * 100; diff --git a/src/plugins/layers/useGrayLine.js b/src/plugins/layers/useGrayLine.js index a7c2503d..9c497646 100644 --- a/src/plugins/layers/useGrayLine.js +++ b/src/plugins/layers/useGrayLine.js @@ -286,24 +286,14 @@ export function useLayer({ enabled = false, opacity = 0.5, map = null }) { const GrayLineControl = L.Control.extend({ options: { position: 'topright' }, onAdd: function () { - const container = L.DomUtil.create('div', 'grayline-control'); - container.style.cssText = ` - background: var(--bg-panel); - padding: 12px; - border-radius: 5px; - font-family: 'JetBrains Mono', monospace; - font-size: 11px; - color: var(--text-primary); - border: 1px solid var(--border-color); - box-shadow: 0 2px 8px rgba(0,0,0,0.3); - min-width: 200px; - `; + const panelWrapper = L.DomUtil.create('div', 'panel-wrapper'); + const container = L.DomUtil.create('div', 'grayline-control', panelWrapper); const now = new Date(); const timeStr = now.toUTCString(); container.innerHTML = ` -