изменение интерфейса тренировок
Gitea Actions Demo / build_and_push (push) Failing after 23s Details

This commit is contained in:
artem 2026-09-05 22:55:16 +03:00
parent cd58beb85a
commit b92af7e58a
1 changed files with 62 additions and 0 deletions

View File

@ -213,6 +213,23 @@
<span class="workout-chart-readout" v-if="readoutValue[config.key]"> <span class="workout-chart-readout" v-if="readoutValue[config.key]">
{{ readoutValue[config.key] }} {{ readoutValue[config.key] }}
</span> </span>
<span
v-if="config.key === 'power' && powerZones"
class="power-zones-legend"
>
<span
v-for="zone in powerZones"
:key="zone.label"
class="power-zone-chip"
:title="zone.label"
>
<span
class="power-zone-color"
:style="{ backgroundColor: zone.color }"
></span>
{{ zone.label }}
</span>
</span>
</h4> </h4>
<LineWithLineChart <LineWithLineChart
:ref="(el) => setChartRef(config.key, el)" :ref="(el) => setChartRef(config.key, el)"
@ -289,6 +306,11 @@ import {
import LineWithLineChart from "./LineWithLineChart.js"; import LineWithLineChart from "./LineWithLineChart.js";
import ChartGroup from "./ChartGroup.js"; import ChartGroup from "./ChartGroup.js";
import {
PowerZonesPlugin,
ftpToZones,
type PowerZone,
} from "./PowerZonesPlugin.js";
import type { LngLat, YMap } from "@yandex/ymaps3-types"; import type { LngLat, YMap } from "@yandex/ymaps3-types";
import { import {
YandexMap, YandexMap,
@ -456,6 +478,22 @@ const sectionBounds = computed<
return bounds; return bounds;
}); });
const activeSection = ref<number | null>(null); const activeSection = ref<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
// memoized per FTP value (computed cache + no re-allocation).
let powerZonesFtp: number | undefined | null = NaN;
let powerZonesMemo: Array<PowerZone> | null = null;
const powerZones = computed<Array<PowerZone> | null>(() => {
const ftp = workoutItem?.power;
if (ftp !== powerZonesFtp) {
powerZonesFtp = ftp;
powerZonesMemo = ftpToZones(ftp);
}
return powerZonesMemo;
});
// Expose the memoized zones to the shared plugin instance (read at draw time).
PowerZonesPlugin.zones = powerZones;
// Inner section boundaries (point indexes, never 0 or the last point). // Inner section boundaries (point indexes, never 0 or the last point).
const sectionBoundaries = computed<Array<number>>(() => const sectionBoundaries = computed<Array<number>>(() =>
sectionBounds.value sectionBounds.value
@ -558,6 +596,10 @@ const chartPlugins = [
} }
}, },
}, },
// Power-zone heatmap: shared instance for all charts; the hook no-ops on
// every canvas except the power one (dataset label check). The zone list
// is exposed through the shared object, same pattern as the mapX ref.
PowerZonesPlugin,
]; ];
/** X scale per mode: time scale for "time", plain meters for "distance". */ /** X scale per mode: time scale for "time", plain meters for "distance". */
const xScaleOptions = (mode: XAxisMode): Record<string, unknown> => { const xScaleOptions = (mode: XAxisMode): Record<string, unknown> => {
@ -1101,4 +1143,24 @@ h3 {
font-weight: normal; font-weight: normal;
color: #07c; color: #07c;
} }
.power-zones-legend {
display: inline-flex;
flex-wrap: wrap;
gap: 4px 8px;
margin-left: 10px;
font-size: 11px;
font-weight: normal;
color: #555;
}
.power-zone-chip {
display: inline-flex;
align-items: center;
gap: 3px;
}
.power-zone-color {
display: inline-block;
width: 10px;
height: 10px;
border-radius: 2px;
}
</style> </style>