summaryrefslogtreecommitdiff
path: root/web/static/ride-detail-boundaries.js
blob: 98e0990fc7874a56c85f172a4939f0f837b66a9d (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
import { climbMetrics, formatDistance, formatElevation } from "./ride-detail-logic.js";

export class RideBoundaryController {
  constructor({ forms, points, climbBounds, onBoundaryChanged, onPointSelected, setMessage }) {
    this.forms = forms;
    this.points = points;
    this.climbBounds = climbBounds;
    this.onBoundaryChanged = onBoundaryChanged;
    this.onPointSelected = onPointSelected;
    this.setMessage = setMessage;
    this.activeBoundary = null;
    this.initialize();
  }

  initialize() {
    for (const form of this.forms) {
      const item = /** @type {HTMLElement} */ (form.closest("[data-climb-item]"));
      const climbIndex = Number.parseInt(item.dataset.climbIndex, 10);
      this.climbBounds[climbIndex] = {
        startIndex: Number.parseInt(/** @type {HTMLInputElement} */ (form.querySelector('[data-boundary-input="start"]')).value, 10),
        endIndex: Number.parseInt(/** @type {HTMLInputElement} */ (form.querySelector('[data-boundary-input="end"]')).value, 10),
      };
      this.updatePreview(form);
      const buttons = [...form.querySelectorAll("[data-boundary-button]")].map((button) => /** @type {HTMLElement} */ (button));
      for (const button of buttons) {
        button.addEventListener("click", () => {
          this.clearSelection();
          this.activeBoundary = { form, target: button.dataset.boundaryButton, source: button.dataset.boundarySource };
          button.classList.add("active");
          const surface = this.activeBoundary.source === "map" ? "map" : "profile";
          this.setMessage(`Click the ${surface} to select the ${this.activeBoundary.target} point.`);
        });
      }
    }
  }

  isSelecting(source) {
    return this.activeBoundary?.source === source;
  }

  clearSelection() {
    this.activeBoundary = null;
    for (const form of this.forms) {
      for (const button of form.querySelectorAll("[data-boundary-button]")) button.classList.remove("active");
    }
  }

  choosePoint(index) {
    if (!this.activeBoundary) return;
    const { form, target } = this.activeBoundary;
    const item = /** @type {HTMLElement} */ (form.closest("[data-climb-item]"));
    const climbIndex = Number.parseInt(item.dataset.climbIndex, 10);
    const input = /** @type {HTMLInputElement} */ (form.querySelector(`[data-boundary-input="${target}"]`));
    const output = /** @type {HTMLOutputElement} */ (form.querySelector(`[data-boundary-output="${target}"]`));
    input.value = index;
    output.textContent = this.boundaryLabel(index);
    this.climbBounds[climbIndex][`${target}Index`] = index;
    this.updatePreview(form);
    this.onBoundaryChanged(climbIndex);
    this.clearSelection();
    this.onPointSelected(index);
  }

  updatePreview(form) {
    const preview = /** @type {HTMLElement} */ (form.querySelector("[data-boundary-preview]"));
    const item = /** @type {HTMLElement} */ (form.closest("[data-climb-item]"));
    const climbIndex = Number.parseInt(item.dataset.climbIndex, 10);
    const metrics = climbMetrics(this.points, this.climbBounds[climbIndex]);
    if (!metrics) {
      preview.textContent = "Choose an end point after the start point.";
      return;
    }
    const summary = /** @type {HTMLElement} */ (item.querySelector("[data-climb-summary]"));
    const metricsOutput = /** @type {HTMLElement} */ (item.querySelector("[data-climb-metrics]"));
    summary.textContent = `${formatDistance(metrics.start.distanceKm)}–${formatDistance(metrics.end.distanceKm)}`;
    metricsOutput.textContent = `${metrics.category} · ${formatDistance(metrics.distanceKm)} at ${metrics.slope.toFixed(1)}% · Cotacol ${metrics.cotacol.toFixed(1)}`;
    preview.textContent = `Preview: ${formatDistance(metrics.distanceKm)} · ${metrics.elevationGain >= 0 ? "+" : ""}${formatElevation(metrics.elevationGain)} · ${metrics.slope.toFixed(1)}% · Cotacol ${metrics.cotacol.toFixed(1)}`;
  }

  boundaryLabel(index) {
    const point = this.points[index];
    return `${formatDistance(point.distanceKm)} · ${formatElevation(point.elevationM)}`;
  }
}