|
71 | 71 | </body> |
72 | 72 | <body> |
73 | 73 | <div id="cesiumContainer" class="cesiumContainer"></div> |
74 | | - <div id="metricsContainer" style="position: absolute; bottom: 0px; right: 0px; width: 200px; height: 250px; touch-action: none; user-select: none;"> |
75 | | - <div class="metrics" style="color: #FFFFFF; width: 100%; height: 100%; margin: 0; border-radius: 25px; border: 2px solid #73AD21; background: #73AD21; padding: 8px; box-sizing: border-box; overflow: hidden; position: relative;"> |
| 74 | + <div id="metricsContainer" style="position: absolute; bottom: 0px; right: 0px; width: 200px; height: 250px; touch-action: none; user-select: none; z-index: 1000;"> |
| 75 | + <div class="metrics" style="color: #FFFFFF; width: 100%; height: 100%; margin: 0; border-radius: 25px; border: 2px solid #73AD21; background: #73AD21; padding: 8px; box-sizing: border-box; overflow: hidden; position: relative; touch-action: none;"> |
76 | 76 | <div id="metricsText" style="font-size: 12px; line-height: 1.4;">🏃Speed: 0.00<br>🚴Cadence:0<br>💓Heart:0<br>🔥Calories:0.0<br>📏Odometer:0.00<br>⚡Watt:0<br>⏲️Elapsed:0:00:00<br>📐Inclination:0.0<br>🧲Resistance:0<br>✈️Altitude:0.0<br>⛰️Elevation:0.0</div> |
77 | | - <div id="resizeHandle" style="position: absolute; bottom: 0; right: 0; width: 20px; height: 20px; background: linear-gradient(135deg, transparent 50%, rgba(255,255,255,0.5) 50%); cursor: nwse-resize; border-bottom-right-radius: 23px;"></div> |
| 77 | + <div id="resizeHandle" style="position: absolute; bottom: 0; right: 0; width: 50px; height: 50px; background: linear-gradient(135deg, transparent 50%, rgba(255,255,255,0.8) 50%); cursor: nwse-resize; border-bottom-right-radius: 23px; display: flex; align-items: flex-end; justify-content: flex-end; font-size: 24px; color: rgba(0,0,0,0.6); padding-bottom: 2px; padding-right: 4px;">⤡</div> |
78 | 78 | </div> |
79 | 79 | </div> |
80 | | - <div style="border: 0px solid #aaa; border-radius: 10px; overflow: hidden; position:absolute; bottom: 0px; right: 150px; width=150px; height=75px"><canvas id="canvasChart" style="width=150px; height=75px; border-right: 0pt solid #ffff00;"></canvas></div> |
| 80 | + <div id="chartContainer" style="border: 0px solid #aaa; border-radius: 10px; overflow: hidden; position:absolute; bottom: 0px; right: 150px; width=150px; height=75px; z-index: 999; touch-action: none;"><canvas id="canvasChart" style="width=150px; height=75px; border-right: 0pt solid #ffff00;"></canvas></div> |
81 | 81 | <script type="text/javascript"> |
82 | 82 | let cameraComplete = true |
83 | 83 | let lastAzimuth = 0 |
|
325 | 325 | const container = document.getElementById('metricsContainer'); |
326 | 326 | const resizeHandle = document.getElementById('resizeHandle'); |
327 | 327 | const metricsText = document.getElementById('metricsText'); |
| 328 | + const chartContainer = document.getElementById('chartContainer'); |
328 | 329 |
|
329 | 330 | let isDragging = false; |
330 | 331 | let isResizing = false; |
331 | 332 | let startX, startY, startLeft, startTop, startWidth, startHeight; |
332 | 333 | let resizeTimeout = null; |
333 | 334 |
|
| 335 | + // Update chart position to follow metrics container |
| 336 | + function updateChartPosition() { |
| 337 | + // Position chart to the left of metrics container, aligned at bottom |
| 338 | + const metricsLeft = container.offsetLeft; |
| 339 | + const metricsTop = container.offsetTop; |
| 340 | + const metricsHeight = container.offsetHeight; |
| 341 | + const chartWidth = chartContainer.offsetWidth; |
| 342 | + const chartHeight = chartContainer.offsetHeight; |
| 343 | + |
| 344 | + // Position chart: 10px to the left of metrics, aligned at bottom |
| 345 | + chartContainer.style.left = (metricsLeft - chartWidth - 10) + 'px'; |
| 346 | + chartContainer.style.top = (metricsTop + metricsHeight - chartHeight) + 'px'; |
| 347 | + chartContainer.style.right = 'auto'; |
| 348 | + chartContainer.style.bottom = 'auto'; |
| 349 | + } |
| 350 | + |
334 | 351 | // Load saved position and size |
335 | 352 | function loadState() { |
336 | 353 | const saved = localStorage.getItem('metricsContainerState'); |
|
343 | 360 | if (state.bottom !== undefined) container.style.bottom = state.bottom + 'px'; |
344 | 361 | if (state.width) container.style.width = state.width + 'px'; |
345 | 362 | if (state.height) container.style.height = state.height + 'px'; |
| 363 | + updateChartPosition(); |
346 | 364 | updateFontSize(); |
347 | 365 | } catch (e) { |
348 | 366 | console.error('Error loading metrics state:', e); |
|
438 | 456 | return { x: e.clientX, y: e.clientY }; |
439 | 457 | } |
440 | 458 |
|
| 459 | + // Check if touch/click is in resize handle area (bottom-right 50x50px) |
| 460 | + function isInResizeHandle(x, y) { |
| 461 | + const rect = container.getBoundingClientRect(); |
| 462 | + const handleSize = 50; |
| 463 | + return ( |
| 464 | + x >= rect.right - handleSize && |
| 465 | + x <= rect.right && |
| 466 | + y >= rect.bottom - handleSize && |
| 467 | + y <= rect.bottom |
| 468 | + ); |
| 469 | + } |
| 470 | + |
441 | 471 | // Start dragging |
442 | 472 | function startDrag(e) { |
443 | 473 | if (isResizing) return; |
|
449 | 479 | startTop = container.offsetTop; |
450 | 480 | container.style.right = 'auto'; |
451 | 481 | container.style.bottom = 'auto'; |
| 482 | + |
| 483 | + // Add global listeners |
| 484 | + document.addEventListener('mousemove', handleMove); |
| 485 | + document.addEventListener('touchmove', handleMove, { passive: false }); |
| 486 | + document.addEventListener('mouseup', endDragOrResize); |
| 487 | + document.addEventListener('touchend', endDragOrResize); |
| 488 | + document.addEventListener('touchcancel', endDragOrResize); |
| 489 | + |
| 490 | + e.stopPropagation(); |
452 | 491 | e.preventDefault(); |
453 | 492 | } |
454 | 493 |
|
|
460 | 499 | startY = coords.y; |
461 | 500 | startWidth = container.offsetWidth; |
462 | 501 | startHeight = container.offsetHeight; |
| 502 | + |
| 503 | + // Add global listeners |
| 504 | + document.addEventListener('mousemove', handleMove); |
| 505 | + document.addEventListener('touchmove', handleMove, { passive: false }); |
| 506 | + document.addEventListener('mouseup', endDragOrResize); |
| 507 | + document.addEventListener('touchend', endDragOrResize); |
| 508 | + document.addEventListener('touchcancel', endDragOrResize); |
| 509 | + |
463 | 510 | e.stopPropagation(); |
464 | 511 | e.preventDefault(); |
465 | 512 | } |
466 | 513 |
|
467 | 514 | // Handle move |
468 | 515 | function handleMove(e) { |
| 516 | + // Only handle if we're actually dragging or resizing |
| 517 | + if (!isDragging && !isResizing) { |
| 518 | + return; |
| 519 | + } |
| 520 | + |
469 | 521 | const coords = getCoordinates(e); |
470 | 522 |
|
471 | 523 | if (isDragging) { |
472 | 524 | const deltaX = coords.x - startX; |
473 | 525 | const deltaY = coords.y - startY; |
474 | 526 | container.style.left = (startLeft + deltaX) + 'px'; |
475 | 527 | container.style.top = (startTop + deltaY) + 'px'; |
| 528 | + updateChartPosition(); |
| 529 | + e.stopPropagation(); |
476 | 530 | e.preventDefault(); |
477 | 531 | } else if (isResizing) { |
478 | 532 | const deltaX = coords.x - startX; |
|
481 | 535 | const newHeight = Math.max(100, startHeight + deltaY); |
482 | 536 | container.style.width = newWidth + 'px'; |
483 | 537 | container.style.height = newHeight + 'px'; |
| 538 | + updateChartPosition(); |
484 | 539 | debouncedUpdateFontSize(); |
| 540 | + e.stopPropagation(); |
485 | 541 | e.preventDefault(); |
486 | 542 | } |
487 | 543 | } |
488 | 544 |
|
489 | 545 | // End drag or resize |
490 | 546 | function endDragOrResize(e) { |
491 | | - if (isDragging || isResizing) { |
| 547 | + const wasDragging = isDragging; |
| 548 | + const wasResizing = isResizing; |
| 549 | + |
| 550 | + // Always reset state first |
| 551 | + isDragging = false; |
| 552 | + isResizing = false; |
| 553 | + |
| 554 | + // Always remove listeners to prevent leaks |
| 555 | + document.removeEventListener('mousemove', handleMove); |
| 556 | + document.removeEventListener('touchmove', handleMove); |
| 557 | + document.removeEventListener('mouseup', endDragOrResize); |
| 558 | + document.removeEventListener('touchend', endDragOrResize); |
| 559 | + document.removeEventListener('touchcancel', endDragOrResize); |
| 560 | + |
| 561 | + // Only save state if we were actually dragging/resizing |
| 562 | + if (wasDragging || wasResizing) { |
492 | 563 | saveState(); |
493 | 564 | // If we were resizing, clear pending timeout and update immediately |
494 | | - if (isResizing) { |
| 565 | + if (wasResizing) { |
495 | 566 | if (resizeTimeout) { |
496 | 567 | clearTimeout(resizeTimeout); |
497 | 568 | resizeTimeout = null; |
498 | 569 | } |
499 | 570 | updateFontSize(); |
500 | 571 | } |
501 | 572 | } |
502 | | - isDragging = false; |
503 | | - isResizing = false; |
504 | 573 | } |
505 | 574 |
|
506 | | - // Add event listeners for dragging (on container, but not on resize handle) |
| 575 | + // Check if touch/click is inside container |
| 576 | + function isInsideContainer(x, y) { |
| 577 | + const rect = container.getBoundingClientRect(); |
| 578 | + return ( |
| 579 | + x >= rect.left && |
| 580 | + x <= rect.right && |
| 581 | + y >= rect.top && |
| 582 | + y <= rect.bottom |
| 583 | + ); |
| 584 | + } |
| 585 | + |
| 586 | + // Add event listeners for dragging/resizing on container |
507 | 587 | container.addEventListener('mousedown', function(e) { |
508 | | - if (e.target !== resizeHandle) startDrag(e); |
| 588 | + const coords = getCoordinates(e); |
| 589 | + // Double check we're actually inside the container |
| 590 | + if (!isInsideContainer(coords.x, coords.y)) { |
| 591 | + return; |
| 592 | + } |
| 593 | + if (isInResizeHandle(coords.x, coords.y)) { |
| 594 | + startResize(e); |
| 595 | + } else { |
| 596 | + startDrag(e); |
| 597 | + } |
509 | 598 | }); |
510 | 599 | container.addEventListener('touchstart', function(e) { |
511 | | - if (e.target !== resizeHandle) startDrag(e); |
512 | | - }); |
513 | | - |
514 | | - // Add event listeners for resizing (on resize handle) |
515 | | - resizeHandle.addEventListener('mousedown', startResize); |
516 | | - resizeHandle.addEventListener('touchstart', startResize); |
517 | | - |
518 | | - // Add global move and end listeners |
519 | | - document.addEventListener('mousemove', handleMove); |
520 | | - document.addEventListener('touchmove', handleMove, { passive: false }); |
521 | | - document.addEventListener('mouseup', endDragOrResize); |
522 | | - document.addEventListener('touchend', endDragOrResize); |
| 600 | + const coords = getCoordinates(e); |
| 601 | + // Double check we're actually inside the container |
| 602 | + if (!isInsideContainer(coords.x, coords.y)) { |
| 603 | + return; |
| 604 | + } |
| 605 | + if (isInResizeHandle(coords.x, coords.y)) { |
| 606 | + startResize(e); |
| 607 | + } else { |
| 608 | + startDrag(e); |
| 609 | + } |
| 610 | + }, { passive: false }); |
523 | 611 |
|
524 | 612 | // Load saved state and set initial font size |
525 | 613 | loadState(); |
526 | 614 | updateFontSize(); |
| 615 | + updateChartPosition(); |
527 | 616 | })(); |
528 | 617 | </script> |
529 | 618 | </body> |
|
0 commit comments