diff --git a/src/App.tsx b/src/App.tsx index 578b754..a428462 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,7 +1,7 @@ import React, { useState, useCallback } from "react"; import { WorkoutPlot } from "./components/WorkoutPlot"; import { WorkoutStats } from "./components/WorkoutStats"; -import { parse, chunkRangeIntervals, Duration } from "zwiftout"; +import { parse } from "zwiftout"; import { ErrorMessage } from "./components/ErrorMessage"; import styled from "styled-components"; import { CodeEditor } from "./components/CodeEditor"; @@ -29,9 +29,6 @@ const AppContainer = styled.div` margin: 0 auto; `; -// Split range-intervals into 1 minute chunks -const chunkSize = new Duration(60); - export function App() { const [text, setText] = useState(defaultWorkout); const [workout, setWorkout] = useState(parse(defaultWorkout)); @@ -54,7 +51,7 @@ export function App() { - + {error && {error}} diff --git a/src/components/Bar.tsx b/src/components/Bar.tsx index 75f3203..378a694 100644 --- a/src/components/Bar.tsx +++ b/src/components/Bar.tsx @@ -1,3 +1,4 @@ +import React from "react"; import styled from "styled-components"; import { ZoneType } from "zwiftout"; @@ -30,3 +31,60 @@ export const Bar = styled.div` background: ${(props) => zoneColorsMap[props.zone]}; transition: width 0.1s, height 0.1s, background-color 0.1s; `; + +export type RangeBarProps = { + startZone: ZoneType; + endZone: ZoneType; + // Percentage of total workout length + durationPercentage: number; + // Percentage of maximum intensity in workout + intensityStartPercentage: number; + intensityEndPercentage: number; +}; + +const createUpPolygon = (middle: number) => `polygon(0% 100%, 100% 100%, 100% 0%, 0% ${middle}%)`; +const createDownPolygon = (middle: number) => `polygon(0% 0%, 0% 100%, 100% 100%, 100% ${middle}%)`; + +export const RangeContainer = styled.div<{ + height: number; + width: number; + startZone: ZoneType; + endZone: ZoneType; + middle: number; + direction: "up" | "down"; +}>` + display: inline-block; + border-bottom-left-radius: 10px; + border-bottom-right-radius: 10px; + vertical-align: bottom; + margin-right: 0.1%; + /* exclude 0.1% margin from bar width */ + width: ${(props) => props.width - 0.1}%; + height: ${(props) => props.height}%; + clip-path: ${({ direction, middle }) => (direction === "up" ? createUpPolygon(middle) : createDownPolygon(middle))}; + background: linear-gradient( + to right, + ${(props) => zoneColorsMap[props.startZone]}, + ${(props) => zoneColorsMap[props.endZone]} + ); + transition: width 0.1s, height 0.1s, background-color 0.1s; +`; + +export const RangeBar: React.FC = (props) => { + const minHeightPercentage = Math.min(props.intensityStartPercentage, props.intensityEndPercentage); + const maxHeightPercentage = Math.max(props.intensityStartPercentage, props.intensityEndPercentage); + const bottomHeight = (minHeightPercentage / maxHeightPercentage) * 100; + const topHeight = 100 - bottomHeight; + const direction = props.intensityStartPercentage < props.intensityEndPercentage ? "up" : "down"; + + return ( + + ); +}; diff --git a/src/components/WorkoutPlot.tsx b/src/components/WorkoutPlot.tsx index 9aee26a..02d0bd1 100644 --- a/src/components/WorkoutPlot.tsx +++ b/src/components/WorkoutPlot.tsx @@ -1,7 +1,15 @@ import React from "react"; import styled from "styled-components"; -import { Interval, Intensity, Duration, totalDuration, maximumIntensity } from "zwiftout"; -import { BarProps, Bar } from "./Bar"; +import { + Interval, + Intensity, + Duration, + totalDuration, + maximumIntensity, + RangeIntensity, + intensityValueToZoneType, +} from "zwiftout"; +import { BarProps, Bar, RangeBar, RangeBarProps } from "./Bar"; const toBarProps = (interval: Interval, workoutDuration: Duration, maxIntensity: Intensity): BarProps => ({ zone: interval.intensity.zone, @@ -18,17 +26,27 @@ const Plot = styled.div` margin: 10px 0; `; +const toRangeBarProps = (interval: Interval, workoutDuration: Duration, maxIntensity: Intensity): RangeBarProps => ({ + startZone: intensityValueToZoneType(interval.intensity.start), + endZone: intensityValueToZoneType(interval.intensity.end), + durationPercentage: (interval.duration.seconds / workoutDuration.seconds) * 100, + intensityStartPercentage: (interval.intensity.start / maxIntensity.value) * 100, + intensityEndPercentage: (interval.intensity.end / maxIntensity.value) * 100, +}); + export const WorkoutPlot: React.FC<{ intervals: Interval[] }> = ({ intervals }) => { const workoutDuration = totalDuration(intervals); const maxIntensity = maximumIntensity(intervals); return ( - {intervals - .map((interval) => toBarProps(interval, workoutDuration, maxIntensity)) - .map((props, i) => ( - - ))} + {intervals.map((interval, i) => { + if (interval.intensity instanceof RangeIntensity) { + return ; + } else { + return ; + } + })} ); };