работа с зумом и участками
Gitea Actions Demo / build_and_push (push) Successful in 1m17s
Details
Gitea Actions Demo / build_and_push (push) Successful in 1m17s
Details
This commit is contained in:
parent
37c92b1e25
commit
7142293591
|
|
@ -2,11 +2,106 @@
|
|||
|
||||
## Task State
|
||||
|
||||
- task_id: TASK-PROFILE-ULID-BUG
|
||||
- task_id: TASK-INTEG-SECTION
|
||||
- status: success
|
||||
- summary: **PATCH /profiles/{id} возвращает 500 (смена имени не персистится). Root cause: `default=str(ULID())` в `profiles.py:32` и `users.py:20` — ULID считается один раз при импорте; `DBProfileRepository.update` включает `id` в SET → `IntegrityError`. Фикс: `default_factory=lambda: str(ULID())` + defensive `del new_dict["id"]` + новый API-тест (PATCH → 200 + персистентность имени).**
|
||||
- summary: **Playwright-интеграционный тест для TASK-SECTION-DURATION-ZOOM: 3 passed. Фиксы 2 багов (setChartRef timing + resetAll programmatic zoom).**
|
||||
- next task: —
|
||||
|
||||
## ✅ CODER SUCCESS REPORT — TASK-INTEG-SECTION (+TASK-SECTION-BUGS)
|
||||
- `../integration/tests/section-duration.spec.ts` — 3 теста (duration row, zoom persist across tabs, reset on all tabs), self-contained (stable owner `itest-section-owner`, API-seed, Chart.js via `canvas.__vueParentComponent.exposed.chart`).
|
||||
- **Bug 1 фикс** (`WorkoutItem.vue` `setChartRef` ~1155): ref пушится сразу (без guard `r.chart`), финализация (`collectCharts` + `applySelectedRangeZoom`) через retry `nextTick` (до 20 попыток) — дождётся асинхронной инициализации Chart.js. `nextTick` добавлен в импорт из "vue" (~строка 440).
|
||||
- **Bug 2 фикс** (`ChartGroup.ts` `resetAll` ~125): после `resetZoom(chart)` явно `x.min=undefined; x.max=undefined; chart.update("none")` (broadcastZoom писал min/max напрямую, минуя плагин). Дополнительно: на время цикла `resetAll` установлена `this.broadcasting=true` (try/finally) — `resetZoom` триггерит `onZoomComplete`, который иначе повторно разносит ещё-зумированный диапазон на остальные чарты; guard `isBroadcasting()` отбрасывает эти колбэки.
|
||||
- `npx playwright test tests/section-duration.spec.ts` → **3 passed** (56s) на docker-стече.
|
||||
- `yarn build` (vue-tsc --noEmit + vite) → exit 0.
|
||||
- Known: диагностический `tests/chartprobe.spec.ts` не удалён (rm отклонён) — удалить вручную, если мешает.
|
||||
|
||||
## TASK-INTEG-SECTION: план делегирования
|
||||
|
||||
**Scope:** 1 новый файл — `../integration/tests/section-duration.spec.ts` + запуск на стеке. Не трогать `src/`.
|
||||
|
||||
**Что тестируется (3 UX-требования, компонент `src/pages/workouts/components/WorkoutItem.vue`, страница `workout_item`):**
|
||||
1. Клик по графику (выделение участка) → в блоке `.area-data` появляется строка «Продолжительность».
|
||||
2. Клик по вкладке «Дистанция» → тот же участок остаётся в zoom (не весь путь).
|
||||
3. Клик по reset → участок сброшен; после смена вкладки → весь путь (reset на всех вкладках).
|
||||
|
||||
**Контракты (проверено Архитектором):**
|
||||
- Детальная страница (приватная, с графиками + вкладками): маршрут `workout_item` → URL `{FRONTEND}/workouts/workouts/{id}` (родитель `workouts` + вложенный `workouts/:id`). Навигация `waitUntil: "domcontentloaded"` (Yandex map держит соединения).
|
||||
- Seed через API (чисто, без UI-dedup): `POST {BACKEND}/api/v0/signup` (200) ИЛИ `POST /api/v0/signin` (fallback на повторном прогоне) → token; `POST /api/v0/attachment/upload` (FormData поле `file`, Bearer) → `{id}`; `POST /api/v0/workouts` `{attachment_id, name}` → `{id}` (400 `ObjectExists` → взять id из `GET /api/v0/workouts`[0]).
|
||||
- Файл: `../../backend/tests/fitness_tracker_data_parsing-main/20240507053735.fit` (тот же, что в `workout-upload-flow.spec.ts`).
|
||||
- Детальный ответ `GET /api/v0/workouts/{id}` → `{ results: [{ timestamp, longitude, latitude, speed, heart_rate, power, elevation, ... }], workout: {...} }` — из него взять полный диапазон (первая/последняя `timestamp`, длина) для порогов zoom.
|
||||
- Chart.js: canvas графиков — `#workout-charts .workout-chart-canvas canvas` (несколько; брать `[0]`). Чертит только если есть метрика (у fixture есть speed). Zoom-состояние: `window.Chart.getChart(canvas).isZoomedOrPanned()` + `chart.options.scales.x.min/max` (broadcastZoom пишет сюда) + `chart.scales.x.min/max`.
|
||||
- Клик по графику (выбор участка) — через Chart.js `onClick` опцию: `mouse.down` → `mouse.move` → `mouse.up` в точке внутри chartArea (не drag, чтобы не было drag-zoom). Координаты взять через `chart.chartArea` (left/right/top/bottom) в `page.evaluate`.
|
||||
- Кнопки вкладок: по тексту «Время» и «Дистанция» (VaButton). Reset: `#zoom-botton`. Блок данных участка: `.area-data` (заголовок «Данные участка»), строка «Продолжительность».
|
||||
|
||||
**Структура спе (self-contained, repeatable):**
|
||||
- `beforeAll`: проверить стек (frontend `<500`, backend `/api/v0/html_test/login`==200), проверить наличие fixture-файла; seed stable owner (`itest-section-owner@example.com`, idempotent signup/signin) + его workout (upload attachment + create; на 400 — id из списка).
|
||||
- 3 независимых `test`'а (каждый: `authedPage(token)` → `openWorkoutDetail(id)` → `expect` canvas visible):
|
||||
1. «duration row on section select»: canvas-click → `expect .area-data` visible → `expect` текст «Продолжительность» в `.area-data`.
|
||||
2. «section zoom persists across tab switch»: canvas-click (zoom) → assert zoomed (x.min<full_min) → click «Дистанция» (кнопка active/primary) → **assert всё ещё zoomed** (x.min≈, не полный диапазон) → click «Время» → assert zoomed (симметрия).
|
||||
3. «reset clears section on all tabs»: canvas-click (zoom) → assert zoomed → click `#zoom-botton` (reset) → assert NOT zoomed → click «Дистанция» → assert NOT zoomed (весь путь).
|
||||
|
||||
**Замечания:**
|
||||
- Не `expect` внутри `page.evaluate` — только `return` данных наружу.
|
||||
- `authedPage`: goto → set `localStorage.token` → goto (паттерн из `workout-upload-flow.spec.ts`).
|
||||
- Node ≥20: запуск через nvm `export PATH="$HOME/.nvm/versions/node/v22.14.0/bin:$PATH"`.
|
||||
- `yarn up` (`docker compose up -d --build`) → ждать backend: `until curl -sf http://localhost:8000/api/v0/html_test/login >/dev/null; do sleep 2; done` → `npx playwright test tests/section-duration.spec.ts`.
|
||||
|
||||
**Acceptance criteria:**
|
||||
1. `../integration/tests/section-duration.spec.ts` создан.
|
||||
2. `npx playwright test tests/section-duration.spec.ts` → **3 passed** на запущенном стеке.
|
||||
3. Не сломать существующие спе (опционально: `npx playwright test --list`).
|
||||
|
||||
## TASK-SECTION-DURATION-ZOOM: план делегирования
|
||||
|
||||
**Scope:** 1 файл — `src/pages/workouts/components/WorkoutItem.vue` (компонент, НЕ страница `src/pages/workouts/WorkoutItem.vue`).
|
||||
|
||||
**Ключевые якоря (текущее состояние кода):**
|
||||
|
||||
- `times` (~строка 1061) — сырые ISO-метки времени всех точек (общие для всех метрик).
|
||||
- `distances` (prop) — накопительные метры на точку.
|
||||
- `activeSection = ref<number | null>(null)` (~строка 661) — номер секции, **зависит от `xAxisMode`** (потому что `sectionBounds` пересчитывается под режим).
|
||||
- `computeAreaData(start, end)` (~строка 590) — считает средние по метрикам в блок «Данные участка» (`areaAvgData`).
|
||||
- `switchXAxis(mode)` (~строка 1135) — сбрасывает `activeSection = null` (баг п.2).
|
||||
- `onZoomComplete` (~строка 873) — `activeSection = null` + `computeAreaData(start,end)`.
|
||||
- `onClick` (~строка 930) — выбор/тоггл секции.
|
||||
- `resetChartZoom()` (~строка 1449) — reset.
|
||||
- `setChartRef` (~строка 1126) — после сбора всех refs делает `group.charts = collectCharts()` (сигнал для re-zoom после remount).
|
||||
- `secondsToDuration` — экспортируется из `../Definitions.vue` (не импортирован сейчас — добавить в импорт).
|
||||
|
||||
**Изменения:**
|
||||
|
||||
1. Заменить `activeSection: number|null` на `selectedRange: { startIdx: number; endIdx: number } | null` (единственный source of truth, **не зависит от режима** — индексы точек).
|
||||
2. В `computeAreaData(start,end)`: добавить `avgData["Продолжительность"] = secondsToDuration(durSec)`, где `durSec = max(0, round((Number(new Date(times[end-1])) - Number(new Date(times[start])))/1000))`. Импортировать `secondsToDuration` из `../Definitions.vue`.
|
||||
3. `onClick`: выбрать `selectedRange = {startIdx, endIdx}` по `sectionBounds`; тоггл-off когда `selectedRange` совпадает (startIdx== и endIdx==); выбрать иначе → `selectedRange`, `computeAreaData`, `broadcastZoom`.
|
||||
4. `onZoomComplete`: `selectedRange = null` (ручной zoom снимает выбор), `computeAreaData(start,end)` сохранить.
|
||||
5. `switchXAxis`: **НЕ** сбрасывать `selectedRange`; сохранить `areaAvgData`/`markedCoordinats` (они режим-независимы). Добавить `applySelectedRangeZoom()`: `labels = xAxisMode==='time' ? times : distances`; `toValue = time? Number(new Date(l)) : Number(l)`; `min=toValue(labels[startIdx])`, `max=toValue(labels[endIdx-1])`; `group.broadcastZoom(min,max)`. Вызвать `applySelectedRangeZoom()` в `setChartRef` после `group.charts = collectCharts()` (re-apply zoom в новом режиме после remount).
|
||||
6. `resetChartZoom()`: `selectedRange = null` (чтобы после reset на другой вкладке тоже был весь путь), остальное как есть (`resetAll`, `areaAvgData={}`, `markedCoordinats=[]`).
|
||||
|
||||
**Почему работает «на всех вкладках»: ** reset обнуляет `selectedRange` → при смене вкладки zoom re-apply вызывается только если `selectedRange` set → после reset всегда весь путь.
|
||||
|
||||
**Acceptance criteria:**
|
||||
|
||||
1. `yarn build` (vue-tsc --noEmit) → exit 0.
|
||||
2. `yarn lint` → 0 ошибок (или зафиксировать пре-экзистинг системный `tsutils.iterateComments`).
|
||||
3. Ручной сценарий (MCP browser): выделить участок → в «Данные участка» есть «Продолжительность» → перейти на вкладку «Дистанция» → та же секция остаётся в zoom (не весь путь) → нажать reset → участок сброшен на всех вкладках.
|
||||
|
||||
## ✅ CODER SUCCESS REPORT — TASK-SECTION-DURATION-ZOOM
|
||||
|
||||
- `src/pages/workouts/components/WorkoutItem.vue`:
|
||||
- импорт `secondsToDuration` добавлен в импорт из `../Definitions.vue` (рядом с `formatTime`).
|
||||
- `activeSection: number|null` → `selectedRange: { startIdx: number; endIdx: number } | null` (единственный source of truth; индексы точек, не зависят от `xAxisMode`).
|
||||
- `computeAreaData(start,end)`: в начале цикла добавлена строка `avgData["Продолжительность"] = secondsToDuration(durSec)`, где `durSec = max(0, round((Number(new Date(times[end-1])) - Number(new Date(times[start])))/1000))`.
|
||||
- `onClick`: тоггл-off по совпадению `selectedRange.startIdx===startIdx && selectedRange.endIdx===endIdx`; иначе `selectedRange={startIdx,endIdx}` + `computeAreaData` + `broadcastZoom`.
|
||||
- `onZoomComplete`: `selectedRange=null` (ручной zoom снимает выбор).
|
||||
- `switchXAxis`: УБРАЛ сброс `activeSection` (выбор сохраняется); `areaAvgData`/`markedCoordinats` не трогаю (режим-независимы). Добавлена `applySelectedRangeZoom()` (labels = time? times : distances; broadcastZoom(min,max)).
|
||||
- `setChartRef`: внутри `if (chartRefs.length === chartConfigs.value.length)` после `group.charts = collectCharts()` вызывается `applySelectedRangeZoom()` (re-apply zoom в новом режиме после remount).
|
||||
- `resetChartZoom`: `selectedRange=null` (после reset на любой вкладке — весь путь).
|
||||
- Verified: `yarn build` (vue-tsc --noEmit) → ✓ built in 7.14s, Done (0 type errors).
|
||||
- Verified: `grep activeSection` → 0 совпадений; `grep selectedRange` → 9 совпадений; `grep secondsToDuration` → импорт + использование (2 совпадения).
|
||||
- Note: `yarn lint` сломан системно — `tsutils.iterateComments is not a function` на всех 73 файлах (пре-экзистинг, дефект env eslint/typescript-estree, не от этих изменений).
|
||||
- Browser verification (MCP ad-hoc): НЕ выполнена — dev server `:5173` и интеграционный стек `:8000` не запущены (не поднимал docker-стек ради ad-hoc проверки). Результат по критериям 1–4.
|
||||
- ✅ Architect review: прочитаны все изменённые блоки (импорт 481, `computeAreaData` 599-606, `selectedRange` 670, `onClick` 997-1008, `onZoomComplete` 921, `switchXAxis` 1164-1173, `applySelectedRangeZoom` 1142-1153 + вызов в `setChartRef` 1161, `resetChartZoom` 1479). Корректность подтверждена: `times`/`distances`/`lineCoordinates` одной длины; re-apply zoom после remount через `setChartRef`; после reset `selectedRange=null` → на любой вкладке весь путь.
|
||||
|
||||
## TASK-PROFILE-ULID-BUG: план делегирования
|
||||
|
||||
**Scope:** `../backend/` (3 файла фикса + 1 новый тест).
|
||||
|
|
|
|||
|
|
@ -1,5 +1,57 @@
|
|||
# Progress — Strava Frontend
|
||||
|
||||
## 2026-09-26 — TASK-INTEG-SECTION + TASK-SECTION-BUGS: интеграционный тест section-duration + фикс 2 багов
|
||||
|
||||
- `../integration/tests/section-duration.spec.ts` (new): 3 Playwright-теста
|
||||
(duration row on section select; zoom persist across Время↔Дистанция;
|
||||
reset clears on all tabs). Self-contained: stable owner
|
||||
`itest-section-owner@example.com` (idempotent signup/signin), API-seed
|
||||
workout (upload + create, dedup-fallback → id из `GET /workouts`[0]),
|
||||
Chart.js access через `canvas.__vueParentComponent.exposed.chart`
|
||||
(глобального `window.Chart` в SPA нет); zoom-детект по
|
||||
`options.scales.x.min/max` vs полный диапазон (`isZoomedOrPanned()`
|
||||
всегда false — zoom применяется прямой записью, минуя плагин).
|
||||
- **Bug 1** (`src/pages/workouts/components/WorkoutItem.vue`,
|
||||
`setChartRef` ~строка 1155): ref пушится сразу (guard `r.chart` убран —
|
||||
Chart.js создаёт инстанс асинхронно); финализация
|
||||
(`group.charts = collectCharts()` + `applySelectedRangeZoom()`) через
|
||||
`tryFinalize(retries)` — retry через `nextTick` (до 20 попыток) до тех
|
||||
пор, пока все рефы не получат `chart`. `nextTick` добавлен в импорт из
|
||||
"vue". Корень бага: на remount после `switchXAxis` `setChartRef`
|
||||
срабатывал до создания chart → `chartRefs` не пополнялся →
|
||||
`applySelectedRangeZoom()` не вызывался → zoom терялся.
|
||||
- **Bug 2** (`src/pages/workouts/components/ChartGroup.ts`, `resetAll`
|
||||
~строка 125): после `resetZoom(chart)` явный сброс
|
||||
`options.scales.x.min/max = undefined` + `chart.update("none")`
|
||||
(broadcastZoom писал min/max напрямую, минуя внутреннее состояние
|
||||
плагина, поэтому `resetZoom` один ничего не сбрасывал). Дополнительная
|
||||
корневая причина: `resetZoom` триггерит `onZoomComplete`, который
|
||||
повторно разносит ещё-зумированный диапазон на остальные чарты
|
||||
(перетирая сброшенный) — в `resetAll` на время цикла
|
||||
`this.broadcasting = true` (try/finally), guard `isBroadcasting()`
|
||||
в `onZoomComplete` отбрасывает эти колбэки.
|
||||
- Verified: `npx playwright test tests/section-duration.spec.ts` →
|
||||
**3 passed** (56s, docker-стек); `yarn build` (vue-tsc --noEmit +
|
||||
vite) → exit 0.
|
||||
- Known: диагностический `../integration/tests/chartprobe.spec.ts` не
|
||||
удалён (rm-команда отклонена окружением) — удалить вручную.
|
||||
|
||||
## 2026-09-26 — TASK-SECTION-DURATION-ZOOM: «Продолжительность» + сохранение выбора участка при смене вкладки
|
||||
|
||||
- `src/pages/workouts/components/WorkoutItem.vue`:
|
||||
- импорт `secondsToDuration` из `../Definitions.vue`.
|
||||
- `activeSection: number|null` → `selectedRange: { startIdx, endIdx } | null` (режим-независимый source of truth, индексы точек).
|
||||
- `computeAreaData`: строка `avgData["Продолжительность"] = secondsToDuration(durSec)` по `times[start]`/`times[end-1]`.
|
||||
- `onClick`: тоггл-off по совпадению индексов (`startIdx` и `endIdx`); иначе `selectedRange={startIdx,endIdx}` + `computeAreaData` + `broadcastZoom`.
|
||||
- `onZoomComplete`: `selectedRange = null` (ручной zoom снимает выбор).
|
||||
- `switchXAxis`: убран сброс выбора; добавлена `applySelectedRangeZoom()`; `areaAvgData`/`markedCoordinats` не сбрасываются.
|
||||
- `setChartRef`: `applySelectedRangeZoom()` после `group.charts = collectCharts()`.
|
||||
- `resetChartZoom`: `selectedRange = null` (после reset — весь путь на всех вкладках).
|
||||
- Verified: `yarn build` → ✓ built in 7.14s (vue-tsc --noEmit 0 ошибок).
|
||||
- Verified: `grep activeSection` → 0 совпадений; `grep selectedRange` → 9; `grep secondsToDuration` → 2 (импорт + использование).
|
||||
- Known issue: `yarn lint` сломан системно — `tsutils.iterateComments is not a function` на всех 73 файлах (дефект окружения eslint/typescript-estree).
|
||||
- Browser (MCP ad-hoc): не выполнена — dev server `:5173` и интеграционный стек `:8000` не запущены (не поднимал docker-стек ради ad-hoc проверки). Результат по критериям 1–4.
|
||||
|
||||
## 2026-09-25 — TASK-PROFILE-ULID-BUG: фикс PATCH /profiles 500 (смена имени не персистится)
|
||||
|
||||
- `../backend/app/modules/users/domains/profiles.py:32` — `default=str(ULID())` → `default_factory=lambda: str(ULID())`.
|
||||
|
|
|
|||
|
|
@ -123,8 +123,25 @@ export class ChartGroup {
|
|||
}
|
||||
|
||||
resetAll(): void {
|
||||
for (const chart of this.charts) {
|
||||
resetZoom(chart);
|
||||
// The plugin's resetZoom fires onZoomComplete, and the app handler
|
||||
// re-broadcasts the visible range to the other charts - which would
|
||||
// re-zoom the charts we just reset. Hold the broadcasting guard for
|
||||
// the whole loop so every reset-triggered onZoomComplete is dropped.
|
||||
this.broadcasting = true;
|
||||
try {
|
||||
for (const chart of this.charts) {
|
||||
resetZoom(chart);
|
||||
// broadcastZoom writes min/max directly on scale options (bypassing
|
||||
// the plugin), so resetZoom alone does not clear them.
|
||||
const x = chart.options?.scales?.x;
|
||||
if (x) {
|
||||
x.min = undefined;
|
||||
x.max = undefined;
|
||||
chart.update("none");
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
this.broadcasting = false;
|
||||
}
|
||||
this.clearHover();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -437,6 +437,7 @@ import {
|
|||
onMounted,
|
||||
onBeforeUnmount,
|
||||
watch,
|
||||
nextTick,
|
||||
} from "vue";
|
||||
import { useToast, useForm } from "vuestic-ui/web-components";
|
||||
import { useI18n } from "vue-i18n";
|
||||
|
|
@ -478,6 +479,7 @@ import {
|
|||
distConvert,
|
||||
speedConvert,
|
||||
formatTime,
|
||||
secondsToDuration,
|
||||
ChartData,
|
||||
ChartDataByMetric,
|
||||
WorkoutPhoto,
|
||||
|
|
@ -595,6 +597,14 @@ const computeAreaData = (start: number, end: number) => {
|
|||
? lineCoordinates.slice(start, end)
|
||||
: [];
|
||||
let avgData: AvgData = {};
|
||||
const durSec = Math.max(
|
||||
0,
|
||||
Math.round(
|
||||
(Number(new Date(times[end - 1])) - Number(new Date(times[start]))) /
|
||||
1000,
|
||||
),
|
||||
);
|
||||
avgData["Продолжительность"] = secondsToDuration(durSec);
|
||||
for (const config of chartConfigs.value) {
|
||||
const values = config.data.datasets[0].data.slice(start, end);
|
||||
const present = values.filter((v) => v !== null);
|
||||
|
|
@ -658,7 +668,7 @@ const sectionBounds = computed<
|
|||
}
|
||||
return bounds;
|
||||
});
|
||||
const activeSection = ref<number | null>(null);
|
||||
const selectedRange = ref<{ startIdx: number; endIdx: number } | null>(null);
|
||||
// Power zones keyed by FTP; FTP is not a separate field, so the average
|
||||
// power is used as the FTP heuristic (Strava-style). Null -> no heatmap.
|
||||
// Absolute watt bounds do not depend on the x-axis mode, so the result is
|
||||
|
|
@ -909,7 +919,7 @@ const buildChartOptions = (
|
|||
}
|
||||
}
|
||||
// A manual zoom ends any earlier section selection.
|
||||
activeSection.value = null;
|
||||
selectedRange.value = null;
|
||||
computeAreaData(start, end);
|
||||
group.broadcastZoom(min, max);
|
||||
},
|
||||
|
|
@ -984,15 +994,19 @@ const buildChartOptions = (
|
|||
if (sectionNo === null) {
|
||||
return;
|
||||
}
|
||||
// Toggle: a second click on the already selected section resets.
|
||||
if (activeSection.value === sectionNo) {
|
||||
activeSection.value = null;
|
||||
// Toggle: a second click on the already selected range resets it.
|
||||
if (
|
||||
selectedRange.value &&
|
||||
selectedRange.value.startIdx === startIdx &&
|
||||
selectedRange.value.endIdx === endIdx
|
||||
) {
|
||||
selectedRange.value = null;
|
||||
group.resetAll();
|
||||
areaAvgData.value = {};
|
||||
markedCoordinats.value = [];
|
||||
return;
|
||||
}
|
||||
activeSection.value = sectionNo;
|
||||
selectedRange.value = { startIdx, endIdx };
|
||||
const min = toValue(labels[startIdx]);
|
||||
const max = toValue(labels[endIdx - 1]);
|
||||
computeAreaData(startIdx, endIdx);
|
||||
|
|
@ -1123,14 +1137,39 @@ const collectCharts = () => {
|
|||
}
|
||||
return charts;
|
||||
};
|
||||
// Re-applies the selected point-range zoom in the current x-axis mode. Used
|
||||
// after a mode change remounts every canvas, so the same section stays
|
||||
// zoomed instead of resetting to the full path.
|
||||
const applySelectedRangeZoom = () => {
|
||||
const r = selectedRange.value;
|
||||
if (!r) {
|
||||
return;
|
||||
}
|
||||
const labels = xAxisMode.value === "time" ? times : distances;
|
||||
const toValue = (l: string | number): number =>
|
||||
xAxisMode.value === "time" ? Number(new Date(l as string)) : Number(l);
|
||||
const min = toValue(labels[r.startIdx]);
|
||||
const max = toValue(labels[r.endIdx - 1]);
|
||||
group.broadcastZoom(min, max);
|
||||
};
|
||||
const setChartRef = (_key: string, el: unknown) => {
|
||||
const r = el as ChartRefEl | null;
|
||||
if (r && r.chart && chartRefs.length < chartConfigs.value.length) {
|
||||
if (!r) return;
|
||||
if (chartRefs.length < chartConfigs.value.length) {
|
||||
chartRefs.push(r);
|
||||
}
|
||||
if (chartRefs.length === chartConfigs.value.length) {
|
||||
group.charts = collectCharts();
|
||||
}
|
||||
const tryFinalize = (retries: number): void => {
|
||||
if (
|
||||
chartRefs.length === chartConfigs.value.length &&
|
||||
chartRefs.every((ref) => !!ref.chart)
|
||||
) {
|
||||
group.charts = collectCharts();
|
||||
applySelectedRangeZoom();
|
||||
} else if (retries > 0) {
|
||||
nextTick(() => tryFinalize(retries - 1));
|
||||
}
|
||||
};
|
||||
tryFinalize(20);
|
||||
};
|
||||
const switchXAxis = (mode: XAxisMode) => {
|
||||
if (mode === xAxisMode.value) {
|
||||
|
|
@ -1140,7 +1179,6 @@ const switchXAxis = (mode: XAxisMode) => {
|
|||
// exactly the new set; hover/zoom state is intentionally reset.
|
||||
chartRefs.length = 0;
|
||||
group.charts = [];
|
||||
activeSection.value = null;
|
||||
xAxisMode.value = mode;
|
||||
};
|
||||
onMounted(() => {
|
||||
|
|
@ -1448,7 +1486,7 @@ const deletePhoto = (photo: WorkoutPhoto) => {
|
|||
|
||||
const resetChartZoom = () => {
|
||||
group.resetAll();
|
||||
activeSection.value = null;
|
||||
selectedRange.value = null;
|
||||
areaAvgData.value = {};
|
||||
markedCoordinats.value = [];
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in New Issue