120 lines
3.0 KiB
TypeScript
120 lines
3.0 KiB
TypeScript
import * as xml from "xml";
|
|
import { Interval, Workout, Comment } from "./ast";
|
|
import { detectRepeats, RepeatedInterval } from "./detectRepeats";
|
|
|
|
// Zwift Workout XML generator
|
|
|
|
const generateTextEvents = (comments: Comment[]): xml.XmlObject[] => {
|
|
return comments.map(({ offset, text }) => ({
|
|
textevent: [{ _attr: { timeoffset: offset.seconds, message: text } }],
|
|
}));
|
|
};
|
|
|
|
const generateRangeInterval = (
|
|
tagName: "Warmup" | "Cooldown",
|
|
{ duration, intensity, cadence, comments }: Interval,
|
|
): xml.XmlObject => {
|
|
return {
|
|
[tagName]: [
|
|
{
|
|
_attr: {
|
|
Duration: duration.seconds,
|
|
PowerLow: intensity.start,
|
|
PowerHigh: intensity.end,
|
|
...(cadence ? { Cadence: cadence } : {}),
|
|
},
|
|
},
|
|
...generateTextEvents(comments),
|
|
],
|
|
};
|
|
};
|
|
|
|
const generateSteadyStateInterval = ({ duration, intensity, cadence, comments }: Interval): xml.XmlObject => {
|
|
return {
|
|
SteadyState: [
|
|
{
|
|
_attr: {
|
|
Duration: duration.seconds,
|
|
Power: intensity.value,
|
|
...(cadence ? { Cadence: cadence } : {}),
|
|
},
|
|
},
|
|
...generateTextEvents(comments),
|
|
],
|
|
};
|
|
};
|
|
|
|
const generateFreeRideInterval = ({ duration, comments }: Interval): xml.XmlObject => {
|
|
return {
|
|
FreeRide: [
|
|
{
|
|
_attr: {
|
|
Duration: duration.seconds,
|
|
},
|
|
},
|
|
...generateTextEvents(comments),
|
|
],
|
|
};
|
|
};
|
|
|
|
const generateRepeatInterval = (repInterval: RepeatedInterval): xml.XmlObject => {
|
|
const [on, off] = repInterval.intervals;
|
|
return {
|
|
IntervalsT: [
|
|
{
|
|
_attr: {
|
|
Repeat: repInterval.times,
|
|
|
|
OnDuration: on.duration.seconds,
|
|
OnPower: on.intensity.start,
|
|
...(on.cadence ? { Cadence: on.cadence } : {}),
|
|
|
|
OffDuration: off.duration.seconds,
|
|
OffPower: off.intensity.end,
|
|
...(off.cadence ? { CadenceResting: off.cadence } : {}),
|
|
},
|
|
},
|
|
...generateTextEvents(repInterval.comments),
|
|
],
|
|
};
|
|
};
|
|
|
|
const generateInterval = (interval: Interval | RepeatedInterval): xml.XmlObject => {
|
|
if (interval.type === "repeat") {
|
|
return generateRepeatInterval(interval);
|
|
}
|
|
|
|
const { intensity } = interval;
|
|
if (intensity.start < intensity.end) {
|
|
return generateRangeInterval("Warmup", interval);
|
|
} else if (intensity.start > intensity.end) {
|
|
return generateRangeInterval("Cooldown", interval);
|
|
} else if (intensity.zone === "free") {
|
|
return generateFreeRideInterval(interval);
|
|
} else {
|
|
return generateSteadyStateInterval(interval);
|
|
}
|
|
};
|
|
|
|
const generateTag = (name: string): xml.XmlObject => {
|
|
return {
|
|
tag: [{ _attr: { name } }],
|
|
};
|
|
};
|
|
|
|
export const generateZwo = ({ name, author, description, tags, intervals }: Workout): string => {
|
|
return xml(
|
|
{
|
|
workout_file: [
|
|
{ name: name },
|
|
{ author: author },
|
|
{ description: description },
|
|
{ tags: tags.map(generateTag) },
|
|
{ sportType: "bike" },
|
|
{ workout: detectRepeats(intervals).map(generateInterval) },
|
|
],
|
|
},
|
|
{ indent: " " },
|
|
);
|
|
};
|