|
1 | 1 | // ==UserScript== |
2 | 2 | // @name APRS Auto-Position for OpenHamClock |
3 | 3 | // @namespace http://tampermonkey.net/ |
4 | | -// @version 1.1 |
| 4 | +// @version 1.2 |
5 | 5 | // @description Automatically updates your station position in OpenHamClock based on APRS beacons |
6 | 6 | // @author DO3EET |
7 | 7 | // @match https://openhamclock.com/* |
|
51 | 51 | const styles = ` |
52 | 52 | #ohc-addon-drawer { |
53 | 53 | position: fixed; |
54 | | - bottom: 20px; |
| 54 | + top: 100px; |
55 | 55 | right: 20px; |
56 | 56 | display: flex; |
57 | 57 | flex-direction: row-reverse; |
58 | 58 | align-items: center; |
59 | 59 | gap: 10px; |
60 | 60 | z-index: 10000; |
61 | 61 | pointer-events: none; |
| 62 | + user-select: none; |
62 | 63 | } |
63 | 64 | #ohc-addon-drawer.ohc-vertical { |
64 | 65 | flex-direction: column-reverse; |
65 | 66 | } |
66 | 67 | .ohc-addon-icon { |
| 68 | + position: relative; |
67 | 69 | width: 45px; |
68 | 70 | height: 45px; |
69 | 71 | background: var(--bg-panel, rgba(17, 24, 32, 0.95)); |
|
80 | 82 | transition: all 0.3s ease; |
81 | 83 | } |
82 | 84 | .ohc-addon-icon:hover { border-color: var(--accent-amber, #ffb432); transform: scale(1.1); } |
83 | | - #ohc-addon-launcher { background: var(--bg-tertiary, #1a2332); color: var(--accent-amber); } |
| 85 | + #ohc-addon-launcher { background: var(--bg-tertiary, #1a2332); color: var(--accent-amber); cursor: move; transition: transform 0.3s ease; } |
84 | 86 | .ohc-addon-item { display: none; } |
85 | 87 |
|
86 | 88 | #ohc-autopos-container { |
|
156 | 158 | if (!drawer) { |
157 | 159 | drawer = document.createElement('div'); |
158 | 160 | drawer.id = 'ohc-addon-drawer'; |
| 161 | + |
| 162 | + const updateLayout = () => { |
| 163 | + if (!drawer) return; |
| 164 | + const rect = drawer.getBoundingClientRect(); |
| 165 | + const winW = window.innerWidth; |
| 166 | + const winH = window.innerHeight; |
| 167 | + |
| 168 | + const isRight = rect.left + rect.width / 2 > winW / 2; |
| 169 | + const isBottom = rect.top + rect.height / 2 > winH / 2; |
| 170 | + const isVert = drawer.classList.contains('ohc-vertical'); |
| 171 | + |
| 172 | + if (isVert) { |
| 173 | + drawer.style.flexDirection = isBottom ? 'column-reverse' : 'column'; |
| 174 | + } else { |
| 175 | + drawer.style.flexDirection = isRight ? 'row-reverse' : 'row'; |
| 176 | + } |
| 177 | + }; |
| 178 | + |
159 | 179 | const savedLayout = localStorage.getItem('ohc_addon_layout') || 'horizontal'; |
160 | 180 | if (savedLayout === 'vertical') drawer.classList.add('ohc-vertical'); |
161 | 181 |
|
| 182 | + const savedPos = JSON.parse(localStorage.getItem('ohc_addon_pos') || '{}'); |
| 183 | + if (savedPos.top) drawer.style.top = savedPos.top; |
| 184 | + if (savedPos.bottom) drawer.style.bottom = savedPos.bottom; |
| 185 | + if (savedPos.left) drawer.style.left = savedPos.left; |
| 186 | + if (savedPos.right) drawer.style.right = savedPos.right; |
| 187 | + |
| 188 | + if (!savedPos.top && !savedPos.bottom) { |
| 189 | + drawer.style.top = '100px'; |
| 190 | + drawer.style.right = '20px'; |
| 191 | + } |
| 192 | + |
162 | 193 | const launcher = document.createElement('div'); |
163 | 194 | launcher.id = 'ohc-addon-launcher'; |
164 | 195 | launcher.className = 'ohc-addon-icon'; |
165 | 196 | launcher.innerHTML = '\uD83E\uDDE9'; |
166 | | - launcher.title = 'L: Toggle | R: Rotate'; |
| 197 | + launcher.title = 'L: Toggle | M: Drag | R: Rotate'; |
| 198 | + |
| 199 | + let isDragging = false; |
| 200 | + let dragTimer = null; |
| 201 | + let wasDragged = false; |
| 202 | + let startX, startY, startTop, startLeft; |
167 | 203 |
|
168 | 204 | launcher.onclick = () => { |
| 205 | + if (wasDragged) { |
| 206 | + wasDragged = false; |
| 207 | + return; |
| 208 | + } |
169 | 209 | const items = document.querySelectorAll('.ohc-addon-item'); |
170 | | - const isHidden = items[0]?.style.display !== 'flex'; |
| 210 | + const isHidden = Array.from(items).some((el) => el.style.display !== 'flex'); |
171 | 211 | items.forEach((el) => (el.style.display = isHidden ? 'flex' : 'none')); |
172 | 212 | launcher.style.transform = isHidden ? 'rotate(90deg)' : 'rotate(0deg)'; |
| 213 | + updateLayout(); |
173 | 214 | }; |
174 | 215 |
|
175 | 216 | launcher.oncontextmenu = (e) => { |
176 | 217 | e.preventDefault(); |
177 | | - const isVert = drawer.classList.toggle('ohc-vertical'); |
178 | | - localStorage.setItem('ohc_addon_layout', isVert ? 'vertical' : 'horizontal'); |
| 218 | + drawer.classList.toggle('ohc-vertical'); |
| 219 | + localStorage.setItem('ohc_addon_layout', drawer.classList.contains('ohc-vertical') ? 'vertical' : 'horizontal'); |
| 220 | + updateLayout(); |
| 221 | + }; |
| 222 | + |
| 223 | + const startDrag = (x, y) => { |
| 224 | + isDragging = true; |
| 225 | + wasDragged = true; |
| 226 | + startX = x; |
| 227 | + startY = y; |
| 228 | + const rect = drawer.getBoundingClientRect(); |
| 229 | + startTop = rect.top; |
| 230 | + startLeft = rect.left; |
| 231 | + launcher.style.cursor = 'grabbing'; |
179 | 232 | }; |
180 | 233 |
|
| 234 | + const handleMove = (x, y) => { |
| 235 | + if (!isDragging) return; |
| 236 | + const dx = x - startX; |
| 237 | + const dy = y - startY; |
| 238 | + drawer.style.top = startTop + dy + 'px'; |
| 239 | + drawer.style.left = startLeft + dx + 'px'; |
| 240 | + drawer.style.right = 'auto'; |
| 241 | + drawer.style.bottom = 'auto'; |
| 242 | + }; |
| 243 | + |
| 244 | + const stopDrag = () => { |
| 245 | + if (!isDragging) return; |
| 246 | + isDragging = false; |
| 247 | + launcher.style.cursor = 'move'; |
| 248 | + |
| 249 | + const rect = drawer.getBoundingClientRect(); |
| 250 | + const winW = window.innerWidth; |
| 251 | + const winH = window.innerHeight; |
| 252 | + const isRight = rect.left + rect.width / 2 > winW / 2; |
| 253 | + const isBottom = rect.top + rect.height / 2 > winH / 2; |
| 254 | + |
| 255 | + const pos = {}; |
| 256 | + if (isRight) { |
| 257 | + drawer.style.left = 'auto'; |
| 258 | + drawer.style.right = Math.max(0, winW - rect.right) + 'px'; |
| 259 | + pos.right = drawer.style.right; |
| 260 | + } else { |
| 261 | + drawer.style.right = 'auto'; |
| 262 | + drawer.style.left = Math.max(0, rect.left) + 'px'; |
| 263 | + pos.left = drawer.style.left; |
| 264 | + } |
| 265 | + |
| 266 | + if (isBottom) { |
| 267 | + drawer.style.top = 'auto'; |
| 268 | + drawer.style.bottom = Math.max(0, winH - rect.bottom) + 'px'; |
| 269 | + pos.bottom = drawer.style.bottom; |
| 270 | + } else { |
| 271 | + drawer.style.bottom = 'auto'; |
| 272 | + drawer.style.top = Math.max(0, rect.top) + 'px'; |
| 273 | + pos.top = drawer.style.top; |
| 274 | + } |
| 275 | + |
| 276 | + localStorage.setItem('ohc_addon_pos', JSON.stringify(pos)); |
| 277 | + updateLayout(); |
| 278 | + }; |
| 279 | + |
| 280 | + launcher.onmousedown = (e) => { |
| 281 | + if (e.button === 1) { |
| 282 | + e.preventDefault(); |
| 283 | + startDrag(e.clientX, e.clientY); |
| 284 | + } else if (e.button === 0) { |
| 285 | + startX = e.clientX; |
| 286 | + startY = e.clientY; |
| 287 | + dragTimer = setTimeout(() => startDrag(e.clientX, e.clientY), 500); |
| 288 | + } |
| 289 | + }; |
| 290 | + |
| 291 | + document.addEventListener('mousemove', (e) => { |
| 292 | + if (!isDragging && dragTimer) { |
| 293 | + if (Math.abs(e.clientX - startX) > 5 || Math.abs(e.clientY - startY) > 5) { |
| 294 | + clearTimeout(dragTimer); |
| 295 | + dragTimer = null; |
| 296 | + } |
| 297 | + } |
| 298 | + handleMove(e.clientX, e.clientY); |
| 299 | + }); |
| 300 | + |
| 301 | + document.addEventListener('mouseup', () => { |
| 302 | + clearTimeout(dragTimer); |
| 303 | + dragTimer = null; |
| 304 | + stopDrag(); |
| 305 | + }); |
| 306 | + |
| 307 | + launcher.ontouchstart = (e) => { |
| 308 | + const touch = e.touches[0]; |
| 309 | + startX = touch.clientX; |
| 310 | + startY = touch.clientY; |
| 311 | + dragTimer = setTimeout(() => { |
| 312 | + startDrag(touch.clientX, touch.clientY); |
| 313 | + if (window.navigator.vibrate) window.navigator.vibrate(20); |
| 314 | + }, 500); |
| 315 | + }; |
| 316 | + |
| 317 | + document.addEventListener( |
| 318 | + 'touchmove', |
| 319 | + (e) => { |
| 320 | + const touch = e.touches[0]; |
| 321 | + if (!isDragging && dragTimer) { |
| 322 | + if (Math.abs(touch.clientX - startX) > 5 || Math.abs(touch.clientY - startY) > 5) { |
| 323 | + clearTimeout(dragTimer); |
| 324 | + dragTimer = null; |
| 325 | + } |
| 326 | + } |
| 327 | + if (isDragging) { |
| 328 | + e.preventDefault(); |
| 329 | + handleMove(touch.clientX, touch.clientY); |
| 330 | + } |
| 331 | + }, |
| 332 | + { passive: false }, |
| 333 | + ); |
| 334 | + |
| 335 | + document.addEventListener('touchend', () => { |
| 336 | + clearTimeout(dragTimer); |
| 337 | + dragTimer = null; |
| 338 | + stopDrag(); |
| 339 | + }); |
| 340 | + |
181 | 341 | drawer.appendChild(launcher); |
182 | 342 | document.body.appendChild(drawer); |
| 343 | + setTimeout(updateLayout, 100); |
183 | 344 | } |
184 | 345 |
|
185 | 346 | const toggleBtn = document.createElement('div'); |
|
0 commit comments